cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

pwm-cros-ec.c (8547B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      3 * Expose a PWM controlled by the ChromeOS EC to the host processor.
      4 *
      5 * Copyright (C) 2016 Google, Inc.
      6 */
      7
      8#include <linux/module.h>
      9#include <linux/platform_data/cros_ec_commands.h>
     10#include <linux/platform_data/cros_ec_proto.h>
     11#include <linux/platform_device.h>
     12#include <linux/pwm.h>
     13#include <linux/slab.h>
     14
     15#include <dt-bindings/mfd/cros_ec.h>
     16
     17/**
     18 * struct cros_ec_pwm_device - Driver data for EC PWM
     19 *
     20 * @dev: Device node
     21 * @ec: Pointer to EC device
     22 * @chip: PWM controller chip
     23 * @use_pwm_type: Use PWM types instead of generic channels
     24 */
     25struct cros_ec_pwm_device {
     26	struct device *dev;
     27	struct cros_ec_device *ec;
     28	struct pwm_chip chip;
     29	bool use_pwm_type;
     30};
     31
     32/**
     33 * struct cros_ec_pwm - per-PWM driver data
     34 * @duty_cycle: cached duty cycle
     35 */
     36struct cros_ec_pwm {
     37	u16 duty_cycle;
     38};
     39
     40static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *c)
     41{
     42	return container_of(c, struct cros_ec_pwm_device, chip);
     43}
     44
     45static int cros_ec_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
     46{
     47	struct cros_ec_pwm *channel;
     48
     49	channel = kzalloc(sizeof(*channel), GFP_KERNEL);
     50	if (!channel)
     51		return -ENOMEM;
     52
     53	pwm_set_chip_data(pwm, channel);
     54
     55	return 0;
     56}
     57
     58static void cros_ec_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
     59{
     60	struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
     61
     62	kfree(channel);
     63}
     64
     65static int cros_ec_dt_type_to_pwm_type(u8 dt_index, u8 *pwm_type)
     66{
     67	switch (dt_index) {
     68	case CROS_EC_PWM_DT_KB_LIGHT:
     69		*pwm_type = EC_PWM_TYPE_KB_LIGHT;
     70		return 0;
     71	case CROS_EC_PWM_DT_DISPLAY_LIGHT:
     72		*pwm_type = EC_PWM_TYPE_DISPLAY_LIGHT;
     73		return 0;
     74	default:
     75		return -EINVAL;
     76	}
     77}
     78
     79static int cros_ec_pwm_set_duty(struct cros_ec_pwm_device *ec_pwm, u8 index,
     80				u16 duty)
     81{
     82	struct cros_ec_device *ec = ec_pwm->ec;
     83	struct {
     84		struct cros_ec_command msg;
     85		struct ec_params_pwm_set_duty params;
     86	} __packed buf;
     87	struct ec_params_pwm_set_duty *params = &buf.params;
     88	struct cros_ec_command *msg = &buf.msg;
     89	int ret;
     90
     91	memset(&buf, 0, sizeof(buf));
     92
     93	msg->version = 0;
     94	msg->command = EC_CMD_PWM_SET_DUTY;
     95	msg->insize = 0;
     96	msg->outsize = sizeof(*params);
     97
     98	params->duty = duty;
     99
    100	if (ec_pwm->use_pwm_type) {
    101		ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
    102		if (ret) {
    103			dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
    104			return ret;
    105		}
    106		params->index = 0;
    107	} else {
    108		params->pwm_type = EC_PWM_TYPE_GENERIC;
    109		params->index = index;
    110	}
    111
    112	return cros_ec_cmd_xfer_status(ec, msg);
    113}
    114
    115static int cros_ec_pwm_get_duty(struct cros_ec_pwm_device *ec_pwm, u8 index)
    116{
    117	struct cros_ec_device *ec = ec_pwm->ec;
    118	struct {
    119		struct cros_ec_command msg;
    120		union {
    121			struct ec_params_pwm_get_duty params;
    122			struct ec_response_pwm_get_duty resp;
    123		};
    124	} __packed buf;
    125	struct ec_params_pwm_get_duty *params = &buf.params;
    126	struct ec_response_pwm_get_duty *resp = &buf.resp;
    127	struct cros_ec_command *msg = &buf.msg;
    128	int ret;
    129
    130	memset(&buf, 0, sizeof(buf));
    131
    132	msg->version = 0;
    133	msg->command = EC_CMD_PWM_GET_DUTY;
    134	msg->insize = sizeof(*resp);
    135	msg->outsize = sizeof(*params);
    136
    137	if (ec_pwm->use_pwm_type) {
    138		ret = cros_ec_dt_type_to_pwm_type(index, &params->pwm_type);
    139		if (ret) {
    140			dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
    141			return ret;
    142		}
    143		params->index = 0;
    144	} else {
    145		params->pwm_type = EC_PWM_TYPE_GENERIC;
    146		params->index = index;
    147	}
    148
    149	ret = cros_ec_cmd_xfer_status(ec, msg);
    150	if (ret < 0)
    151		return ret;
    152
    153	return resp->duty;
    154}
    155
    156static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
    157			     const struct pwm_state *state)
    158{
    159	struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
    160	struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
    161	u16 duty_cycle;
    162	int ret;
    163
    164	/* The EC won't let us change the period */
    165	if (state->period != EC_PWM_MAX_DUTY)
    166		return -EINVAL;
    167
    168	if (state->polarity != PWM_POLARITY_NORMAL)
    169		return -EINVAL;
    170
    171	/*
    172	 * EC doesn't separate the concept of duty cycle and enabled, but
    173	 * kernel does. Translate.
    174	 */
    175	duty_cycle = state->enabled ? state->duty_cycle : 0;
    176
    177	ret = cros_ec_pwm_set_duty(ec_pwm, pwm->hwpwm, duty_cycle);
    178	if (ret < 0)
    179		return ret;
    180
    181	channel->duty_cycle = state->duty_cycle;
    182
    183	return 0;
    184}
    185
    186static void cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
    187				  struct pwm_state *state)
    188{
    189	struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
    190	struct cros_ec_pwm *channel = pwm_get_chip_data(pwm);
    191	int ret;
    192
    193	ret = cros_ec_pwm_get_duty(ec_pwm, pwm->hwpwm);
    194	if (ret < 0) {
    195		dev_err(chip->dev, "error getting initial duty: %d\n", ret);
    196		return;
    197	}
    198
    199	state->enabled = (ret > 0);
    200	state->period = EC_PWM_MAX_DUTY;
    201
    202	/*
    203	 * Note that "disabled" and "duty cycle == 0" are treated the same. If
    204	 * the cached duty cycle is not zero, used the cached duty cycle. This
    205	 * ensures that the configured duty cycle is kept across a disable and
    206	 * enable operation and avoids potentially confusing consumers.
    207	 *
    208	 * For the case of the initial hardware readout, channel->duty_cycle
    209	 * will be 0 and the actual duty cycle read from the EC is used.
    210	 */
    211	if (ret == 0 && channel->duty_cycle > 0)
    212		state->duty_cycle = channel->duty_cycle;
    213	else
    214		state->duty_cycle = ret;
    215}
    216
    217static struct pwm_device *
    218cros_ec_pwm_xlate(struct pwm_chip *pc, const struct of_phandle_args *args)
    219{
    220	struct pwm_device *pwm;
    221
    222	if (args->args[0] >= pc->npwm)
    223		return ERR_PTR(-EINVAL);
    224
    225	pwm = pwm_request_from_chip(pc, args->args[0], NULL);
    226	if (IS_ERR(pwm))
    227		return pwm;
    228
    229	/* The EC won't let us change the period */
    230	pwm->args.period = EC_PWM_MAX_DUTY;
    231
    232	return pwm;
    233}
    234
    235static const struct pwm_ops cros_ec_pwm_ops = {
    236	.request = cros_ec_pwm_request,
    237	.free = cros_ec_pwm_free,
    238	.get_state	= cros_ec_pwm_get_state,
    239	.apply		= cros_ec_pwm_apply,
    240	.owner		= THIS_MODULE,
    241};
    242
    243/*
    244 * Determine the number of supported PWMs. The EC does not return the number
    245 * of PWMs it supports directly, so we have to read the pwm duty cycle for
    246 * subsequent channels until we get an error.
    247 */
    248static int cros_ec_num_pwms(struct cros_ec_pwm_device *ec_pwm)
    249{
    250	int i, ret;
    251
    252	/* The index field is only 8 bits */
    253	for (i = 0; i <= U8_MAX; i++) {
    254		ret = cros_ec_pwm_get_duty(ec_pwm, i);
    255		/*
    256		 * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM
    257		 * responses; everything else is treated as an error.
    258		 * The EC error codes map to -EOPNOTSUPP and -EINVAL,
    259		 * so check for those.
    260		 */
    261		switch (ret) {
    262		case -EOPNOTSUPP:	/* invalid command */
    263			return -ENODEV;
    264		case -EINVAL:		/* invalid parameter */
    265			return i;
    266		default:
    267			if (ret < 0)
    268				return ret;
    269			break;
    270		}
    271	}
    272
    273	return U8_MAX;
    274}
    275
    276static int cros_ec_pwm_probe(struct platform_device *pdev)
    277{
    278	struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
    279	struct device *dev = &pdev->dev;
    280	struct device_node *np = pdev->dev.of_node;
    281	struct cros_ec_pwm_device *ec_pwm;
    282	struct pwm_chip *chip;
    283	int ret;
    284
    285	if (!ec) {
    286		dev_err(dev, "no parent EC device\n");
    287		return -EINVAL;
    288	}
    289
    290	ec_pwm = devm_kzalloc(dev, sizeof(*ec_pwm), GFP_KERNEL);
    291	if (!ec_pwm)
    292		return -ENOMEM;
    293	chip = &ec_pwm->chip;
    294	ec_pwm->ec = ec;
    295
    296	if (of_device_is_compatible(np, "google,cros-ec-pwm-type"))
    297		ec_pwm->use_pwm_type = true;
    298
    299	/* PWM chip */
    300	chip->dev = dev;
    301	chip->ops = &cros_ec_pwm_ops;
    302	chip->of_xlate = cros_ec_pwm_xlate;
    303	chip->of_pwm_n_cells = 1;
    304
    305	if (ec_pwm->use_pwm_type) {
    306		chip->npwm = CROS_EC_PWM_DT_COUNT;
    307	} else {
    308		ret = cros_ec_num_pwms(ec_pwm);
    309		if (ret < 0) {
    310			dev_err(dev, "Couldn't find PWMs: %d\n", ret);
    311			return ret;
    312		}
    313		chip->npwm = ret;
    314	}
    315
    316	dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);
    317
    318	ret = pwmchip_add(chip);
    319	if (ret < 0) {
    320		dev_err(dev, "cannot register PWM: %d\n", ret);
    321		return ret;
    322	}
    323
    324	platform_set_drvdata(pdev, ec_pwm);
    325
    326	return ret;
    327}
    328
    329static int cros_ec_pwm_remove(struct platform_device *dev)
    330{
    331	struct cros_ec_pwm_device *ec_pwm = platform_get_drvdata(dev);
    332	struct pwm_chip *chip = &ec_pwm->chip;
    333
    334	pwmchip_remove(chip);
    335
    336	return 0;
    337}
    338
    339#ifdef CONFIG_OF
    340static const struct of_device_id cros_ec_pwm_of_match[] = {
    341	{ .compatible = "google,cros-ec-pwm" },
    342	{ .compatible = "google,cros-ec-pwm-type" },
    343	{},
    344};
    345MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
    346#endif
    347
    348static struct platform_driver cros_ec_pwm_driver = {
    349	.probe = cros_ec_pwm_probe,
    350	.remove = cros_ec_pwm_remove,
    351	.driver = {
    352		.name = "cros-ec-pwm",
    353		.of_match_table = of_match_ptr(cros_ec_pwm_of_match),
    354	},
    355};
    356module_platform_driver(cros_ec_pwm_driver);
    357
    358MODULE_ALIAS("platform:cros-ec-pwm");
    359MODULE_DESCRIPTION("ChromeOS EC PWM driver");
    360MODULE_LICENSE("GPL v2");