1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright 2024 Google LLC |
4 | * |
5 | * This driver provides the ability to control GPIOs on the Chrome OS EC. |
6 | * There isn't any direction control, and setting values on GPIOs is only |
7 | * possible when the system is unlocked. |
8 | */ |
9 | |
10 | #include <linux/bitops.h> |
11 | #include <linux/device.h> |
12 | #include <linux/errno.h> |
13 | #include <linux/gpio/driver.h> |
14 | #include <linux/kernel.h> |
15 | #include <linux/module.h> |
16 | #include <linux/platform_data/cros_ec_commands.h> |
17 | #include <linux/platform_data/cros_ec_proto.h> |
18 | #include <linux/platform_device.h> |
19 | #include <linux/property.h> |
20 | #include <linux/slab.h> |
21 | |
22 | /* Prefix all names to avoid collisions with EC <-> AP nets */ |
23 | static const char cros_ec_gpio_prefix[] = "EC:" ; |
24 | |
25 | /* Setting gpios is only supported when the system is unlocked */ |
26 | static void cros_ec_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val) |
27 | { |
28 | const char *name = gc->names[gpio] + strlen(cros_ec_gpio_prefix); |
29 | struct cros_ec_device *cros_ec = gpiochip_get_data(gc); |
30 | struct ec_params_gpio_set params = { |
31 | .val = val, |
32 | }; |
33 | int ret; |
34 | ssize_t copied; |
35 | |
36 | copied = strscpy(params.name, name, sizeof(params.name)); |
37 | if (copied < 0) |
38 | return; |
39 | |
40 | ret = cros_ec_cmd(ec_dev: cros_ec, version: 0, EC_CMD_GPIO_SET, outdata: ¶ms, |
41 | outsize: sizeof(params), NULL, insize: 0); |
42 | if (ret < 0) |
43 | dev_err(gc->parent, "error setting gpio%d (%s) on EC: %d\n" , gpio, name, ret); |
44 | } |
45 | |
46 | static int cros_ec_gpio_get(struct gpio_chip *gc, unsigned int gpio) |
47 | { |
48 | const char *name = gc->names[gpio] + strlen(cros_ec_gpio_prefix); |
49 | struct cros_ec_device *cros_ec = gpiochip_get_data(gc); |
50 | struct ec_params_gpio_get params; |
51 | struct ec_response_gpio_get response; |
52 | int ret; |
53 | ssize_t copied; |
54 | |
55 | copied = strscpy(params.name, name, sizeof(params.name)); |
56 | if (copied < 0) |
57 | return -EINVAL; |
58 | |
59 | ret = cros_ec_cmd(ec_dev: cros_ec, version: 0, EC_CMD_GPIO_GET, outdata: ¶ms, |
60 | outsize: sizeof(params), indata: &response, insize: sizeof(response)); |
61 | if (ret < 0) { |
62 | dev_err(gc->parent, "error getting gpio%d (%s) on EC: %d\n" , gpio, name, ret); |
63 | return ret; |
64 | } |
65 | |
66 | return response.val; |
67 | } |
68 | |
69 | #define CROS_EC_GPIO_INPUT BIT(8) |
70 | #define CROS_EC_GPIO_OUTPUT BIT(9) |
71 | |
72 | static int cros_ec_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio) |
73 | { |
74 | const char *name = gc->names[gpio] + strlen(cros_ec_gpio_prefix); |
75 | struct cros_ec_device *cros_ec = gpiochip_get_data(gc); |
76 | struct ec_params_gpio_get_v1 params = { |
77 | .subcmd = EC_GPIO_GET_INFO, |
78 | .get_info.index = gpio, |
79 | }; |
80 | struct ec_response_gpio_get_v1 response; |
81 | int ret; |
82 | |
83 | ret = cros_ec_cmd(ec_dev: cros_ec, version: 1, EC_CMD_GPIO_GET, outdata: ¶ms, |
84 | outsize: sizeof(params), indata: &response, insize: sizeof(response)); |
85 | if (ret < 0) { |
86 | dev_err(gc->parent, "error getting direction of gpio%d (%s) on EC: %d\n" , gpio, name, ret); |
87 | return ret; |
88 | } |
89 | |
90 | if (response.get_info.flags & CROS_EC_GPIO_INPUT) |
91 | return GPIO_LINE_DIRECTION_IN; |
92 | |
93 | if (response.get_info.flags & CROS_EC_GPIO_OUTPUT) |
94 | return GPIO_LINE_DIRECTION_OUT; |
95 | |
96 | return -EINVAL; |
97 | } |
98 | |
99 | /* Query EC for all gpio line names */ |
100 | static int cros_ec_gpio_init_names(struct cros_ec_device *cros_ec, struct gpio_chip *gc) |
101 | { |
102 | struct ec_params_gpio_get_v1 params = { |
103 | .subcmd = EC_GPIO_GET_INFO, |
104 | }; |
105 | struct ec_response_gpio_get_v1 response; |
106 | int ret, i; |
107 | /* EC may not NUL terminate */ |
108 | size_t name_len = strlen(cros_ec_gpio_prefix) + sizeof(response.get_info.name) + 1; |
109 | ssize_t copied; |
110 | const char **names; |
111 | char *str; |
112 | |
113 | names = devm_kcalloc(dev: gc->parent, n: gc->ngpio, size: sizeof(*names), GFP_KERNEL); |
114 | if (!names) |
115 | return -ENOMEM; |
116 | gc->names = names; |
117 | |
118 | str = devm_kcalloc(dev: gc->parent, n: gc->ngpio, size: name_len, GFP_KERNEL); |
119 | if (!str) |
120 | return -ENOMEM; |
121 | |
122 | /* Get gpio line names one at a time */ |
123 | for (i = 0; i < gc->ngpio; i++) { |
124 | params.get_info.index = i; |
125 | ret = cros_ec_cmd(ec_dev: cros_ec, version: 1, EC_CMD_GPIO_GET, outdata: ¶ms, |
126 | outsize: sizeof(params), indata: &response, insize: sizeof(response)); |
127 | if (ret < 0) { |
128 | dev_err_probe(dev: gc->parent, err: ret, fmt: "error getting gpio%d info\n" , i); |
129 | return ret; |
130 | } |
131 | |
132 | names[i] = str; |
133 | copied = scnprintf(buf: str, size: name_len, fmt: "%s%s" , cros_ec_gpio_prefix, |
134 | response.get_info.name); |
135 | if (copied < 0) |
136 | return copied; |
137 | |
138 | str += copied + 1; |
139 | } |
140 | |
141 | return 0; |
142 | } |
143 | |
144 | /* Query EC for number of gpios */ |
145 | static int cros_ec_gpio_ngpios(struct cros_ec_device *cros_ec) |
146 | { |
147 | struct ec_params_gpio_get_v1 params = { |
148 | .subcmd = EC_GPIO_GET_COUNT, |
149 | }; |
150 | struct ec_response_gpio_get_v1 response; |
151 | int ret; |
152 | |
153 | ret = cros_ec_cmd(ec_dev: cros_ec, version: 1, EC_CMD_GPIO_GET, outdata: ¶ms, |
154 | outsize: sizeof(params), indata: &response, insize: sizeof(response)); |
155 | if (ret < 0) |
156 | return ret; |
157 | |
158 | return response.get_count.val; |
159 | } |
160 | |
161 | static int cros_ec_gpio_probe(struct platform_device *pdev) |
162 | { |
163 | struct device *dev = &pdev->dev; |
164 | struct device *parent = dev->parent; |
165 | struct cros_ec_dev *ec_dev = dev_get_drvdata(dev: parent); |
166 | struct cros_ec_device *cros_ec = ec_dev->ec_dev; |
167 | struct gpio_chip *gc; |
168 | int ngpios; |
169 | int ret; |
170 | |
171 | /* Use the fwnode from the protocol device, e.g. cros-ec-spi */ |
172 | device_set_node(dev, dev_fwnode(cros_ec->dev)); |
173 | |
174 | ngpios = cros_ec_gpio_ngpios(cros_ec); |
175 | if (ngpios < 0) { |
176 | dev_err_probe(dev, err: ngpios, fmt: "error getting gpio count\n" ); |
177 | return ngpios; |
178 | } |
179 | |
180 | gc = devm_kzalloc(dev, size: sizeof(*gc), GFP_KERNEL); |
181 | if (!gc) |
182 | return -ENOMEM; |
183 | |
184 | gc->ngpio = ngpios; |
185 | gc->parent = dev; |
186 | ret = cros_ec_gpio_init_names(cros_ec, gc); |
187 | if (ret) |
188 | return ret; |
189 | |
190 | gc->can_sleep = true; |
191 | gc->label = dev_name(dev); |
192 | gc->base = -1; |
193 | gc->set = cros_ec_gpio_set; |
194 | gc->get = cros_ec_gpio_get; |
195 | gc->get_direction = cros_ec_gpio_get_direction; |
196 | |
197 | return devm_gpiochip_add_data(dev, gc, cros_ec); |
198 | } |
199 | |
200 | static struct platform_driver cros_ec_gpio_driver = { |
201 | .probe = cros_ec_gpio_probe, |
202 | .driver = { |
203 | .name = "cros-ec-gpio" , |
204 | }, |
205 | }; |
206 | module_platform_driver(cros_ec_gpio_driver); |
207 | |
208 | MODULE_DESCRIPTION("ChromeOS EC GPIO Driver" ); |
209 | MODULE_LICENSE("GPL" ); |
210 | |