Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
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/of.h>
10#include <linux/platform_data/cros_ec_commands.h>
11#include <linux/platform_data/cros_ec_proto.h>
12#include <linux/platform_device.h>
13#include <linux/pwm.h>
14#include <linux/slab.h>
15
16#include <dt-bindings/mfd/cros_ec.h>
17
18/**
19 * struct cros_ec_pwm_device - Driver data for EC PWM
20 *
21 * @ec: Pointer to EC device
22 * @use_pwm_type: Use PWM types instead of generic channels
23 */
24struct cros_ec_pwm_device {
25 struct cros_ec_device *ec;
26 bool use_pwm_type;
27};
28
29static inline struct cros_ec_pwm_device *pwm_to_cros_ec_pwm(struct pwm_chip *chip)
30{
31 return pwmchip_get_drvdata(chip);
32}
33
34static int cros_ec_dt_type_to_pwm_type(u8 dt_index, u8 *pwm_type)
35{
36 switch (dt_index) {
37 case CROS_EC_PWM_DT_KB_LIGHT:
38 *pwm_type = EC_PWM_TYPE_KB_LIGHT;
39 return 0;
40 case CROS_EC_PWM_DT_DISPLAY_LIGHT:
41 *pwm_type = EC_PWM_TYPE_DISPLAY_LIGHT;
42 return 0;
43 default:
44 return -EINVAL;
45 }
46}
47
48static int cros_ec_pwm_set_duty(struct cros_ec_pwm_device *ec_pwm, u8 index,
49 u16 duty)
50{
51 struct cros_ec_device *ec = ec_pwm->ec;
52 TRAILING_OVERLAP(struct cros_ec_command, msg, data,
53 struct ec_params_pwm_set_duty params;
54 ) __packed buf;
55 struct ec_params_pwm_set_duty *params = &buf.params;
56 struct cros_ec_command *msg = &buf.msg;
57 int ret;
58
59 memset(&buf, 0, sizeof(buf));
60
61 msg->version = 0;
62 msg->command = EC_CMD_PWM_SET_DUTY;
63 msg->insize = 0;
64 msg->outsize = sizeof(*params);
65
66 params->duty = duty;
67
68 if (ec_pwm->use_pwm_type) {
69 ret = cros_ec_dt_type_to_pwm_type(index, ¶ms->pwm_type);
70 if (ret) {
71 dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
72 return ret;
73 }
74 params->index = 0;
75 } else {
76 params->pwm_type = EC_PWM_TYPE_GENERIC;
77 params->index = index;
78 }
79
80 return cros_ec_cmd_xfer_status(ec, msg);
81}
82
83static int cros_ec_pwm_get_duty(struct cros_ec_device *ec, bool use_pwm_type, u8 index)
84{
85 TRAILING_OVERLAP(struct cros_ec_command, msg, data,
86 union {
87 struct ec_params_pwm_get_duty params;
88 struct ec_response_pwm_get_duty resp;
89 };
90 ) __packed buf;
91 struct ec_params_pwm_get_duty *params = &buf.params;
92 struct ec_response_pwm_get_duty *resp = &buf.resp;
93 struct cros_ec_command *msg = &buf.msg;
94 int ret;
95
96 memset(&buf, 0, sizeof(buf));
97
98 msg->version = 0;
99 msg->command = EC_CMD_PWM_GET_DUTY;
100 msg->insize = sizeof(*resp);
101 msg->outsize = sizeof(*params);
102
103 if (use_pwm_type) {
104 ret = cros_ec_dt_type_to_pwm_type(index, ¶ms->pwm_type);
105 if (ret) {
106 dev_err(ec->dev, "Invalid PWM type index: %d\n", index);
107 return ret;
108 }
109 params->index = 0;
110 } else {
111 params->pwm_type = EC_PWM_TYPE_GENERIC;
112 params->index = index;
113 }
114
115 ret = cros_ec_cmd_xfer_status(ec, msg);
116 if (ret < 0)
117 return ret;
118
119 return resp->duty;
120}
121
122static int cros_ec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
123 const struct pwm_state *state)
124{
125 struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
126 u16 duty_cycle;
127 int ret;
128
129 /* The EC won't let us change the period */
130 if (state->period != EC_PWM_MAX_DUTY)
131 return -EINVAL;
132
133 if (state->polarity != PWM_POLARITY_NORMAL)
134 return -EINVAL;
135
136 /*
137 * EC doesn't separate the concept of duty cycle and enabled, but
138 * kernel does. Translate.
139 */
140 duty_cycle = state->enabled ? state->duty_cycle : 0;
141
142 ret = cros_ec_pwm_set_duty(ec_pwm, pwm->hwpwm, duty_cycle);
143 if (ret < 0)
144 return ret;
145
146 return 0;
147}
148
149static int cros_ec_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
150 struct pwm_state *state)
151{
152 struct cros_ec_pwm_device *ec_pwm = pwm_to_cros_ec_pwm(chip);
153 int ret;
154
155 ret = cros_ec_pwm_get_duty(ec_pwm->ec, ec_pwm->use_pwm_type, pwm->hwpwm);
156 if (ret < 0) {
157 dev_err(pwmchip_parent(chip), "error getting initial duty: %d\n", ret);
158 return ret;
159 }
160
161 state->enabled = (ret > 0);
162 state->duty_cycle = ret;
163 state->period = EC_PWM_MAX_DUTY;
164 state->polarity = PWM_POLARITY_NORMAL;
165
166 return 0;
167}
168
169static const struct pwm_ops cros_ec_pwm_ops = {
170 .get_state = cros_ec_pwm_get_state,
171 .apply = cros_ec_pwm_apply,
172};
173
174/*
175 * Determine the number of supported PWMs. The EC does not return the number
176 * of PWMs it supports directly, so we have to read the pwm duty cycle for
177 * subsequent channels until we get an error.
178 */
179static int cros_ec_num_pwms(struct cros_ec_device *ec)
180{
181 int i, ret;
182
183 /* The index field is only 8 bits */
184 for (i = 0; i <= U8_MAX; i++) {
185 /*
186 * Note that this function is only called when use_pwm_type is
187 * false. With use_pwm_type == true the number of PWMs is fixed.
188 */
189 ret = cros_ec_pwm_get_duty(ec, false, i);
190 /*
191 * We look for SUCCESS, INVALID_COMMAND, or INVALID_PARAM
192 * responses; everything else is treated as an error.
193 * The EC error codes map to -EOPNOTSUPP and -EINVAL,
194 * so check for those.
195 */
196 switch (ret) {
197 case -EOPNOTSUPP: /* invalid command */
198 return -ENODEV;
199 case -EINVAL: /* invalid parameter */
200 return i;
201 default:
202 if (ret < 0)
203 return ret;
204 break;
205 }
206 }
207
208 return U8_MAX;
209}
210
211static int cros_ec_pwm_probe(struct platform_device *pdev)
212{
213 struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
214 struct device *dev = &pdev->dev;
215 struct device_node *np = pdev->dev.of_node;
216 struct cros_ec_pwm_device *ec_pwm;
217 struct pwm_chip *chip;
218 bool use_pwm_type = false;
219 unsigned int i, npwm;
220 int ret;
221
222 if (!ec)
223 return dev_err_probe(dev, -EINVAL, "no parent EC device\n");
224
225 if (of_device_is_compatible(np, "google,cros-ec-pwm-type")) {
226 use_pwm_type = true;
227 npwm = CROS_EC_PWM_DT_COUNT;
228 } else {
229 ret = cros_ec_num_pwms(ec);
230 if (ret < 0)
231 return dev_err_probe(dev, ret, "Couldn't find PWMs\n");
232 npwm = ret;
233 }
234
235 chip = devm_pwmchip_alloc(dev, npwm, sizeof(*ec_pwm));
236 if (IS_ERR(chip))
237 return PTR_ERR(chip);
238
239 ec_pwm = pwm_to_cros_ec_pwm(chip);
240 ec_pwm->use_pwm_type = use_pwm_type;
241 ec_pwm->ec = ec;
242
243 /* PWM chip */
244 chip->ops = &cros_ec_pwm_ops;
245
246 /*
247 * The device tree binding for this device is special as it only uses a
248 * single cell (for the hwid) and so doesn't provide a default period.
249 * This isn't a big problem though as the hardware only supports a
250 * single period length, it's just a bit ugly to make this fit into the
251 * pwm core abstractions. So initialize the period here, as
252 * of_pwm_xlate_with_flags() won't do that for us.
253 */
254 for (i = 0; i < npwm; ++i)
255 chip->pwms[i].args.period = EC_PWM_MAX_DUTY;
256
257 dev_dbg(dev, "Probed %u PWMs\n", chip->npwm);
258
259 ret = devm_pwmchip_add(dev, chip);
260 if (ret < 0)
261 return dev_err_probe(dev, ret, "cannot register PWM\n");
262
263 return 0;
264}
265
266#ifdef CONFIG_OF
267static const struct of_device_id cros_ec_pwm_of_match[] = {
268 { .compatible = "google,cros-ec-pwm" },
269 { .compatible = "google,cros-ec-pwm-type" },
270 {},
271};
272MODULE_DEVICE_TABLE(of, cros_ec_pwm_of_match);
273#endif
274
275static struct platform_driver cros_ec_pwm_driver = {
276 .probe = cros_ec_pwm_probe,
277 .driver = {
278 .name = "cros-ec-pwm",
279 .of_match_table = of_match_ptr(cros_ec_pwm_of_match),
280 },
281};
282module_platform_driver(cros_ec_pwm_driver);
283
284MODULE_ALIAS("platform:cros-ec-pwm");
285MODULE_DESCRIPTION("ChromeOS EC PWM driver");
286MODULE_LICENSE("GPL v2");