summaryrefslogtreecommitdiff
path: root/drivers/gpio/gpio-cros-ec.c
blob: 0c09bb54dc0c9e68acd03529621090006b83288c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright 2024 Google LLC
 *
 * This driver provides the ability to control GPIOs on the Chrome OS EC.
 * There isn't any direction control, and setting values on GPIOs is only
 * possible when the system is unlocked.
 */

#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/gpio/driver.h>
#include <linux/kernel.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_data/cros_ec_commands.h>
#include <linux/platform_data/cros_ec_proto.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>

/* Prefix all names to avoid collisions with EC <-> AP nets */
static const char cros_ec_gpio_prefix[] = "EC:";

/* Setting gpios is only supported when the system is unlocked */
static void cros_ec_gpio_set(struct gpio_chip *gc, unsigned int gpio, int val)
{
	const char *name = gc->names[gpio] + strlen(cros_ec_gpio_prefix);
	struct cros_ec_device *cros_ec = gpiochip_get_data(gc);
	struct ec_params_gpio_set params = {
		.val = val,
	};
	int ret;
	ssize_t copied;

	copied = strscpy(params.name, name, sizeof(params.name));
	if (copied < 0)
		return;

	ret = cros_ec_cmd(cros_ec, 0, EC_CMD_GPIO_SET, &params,
			  sizeof(params), NULL, 0);
	if (ret < 0)
		dev_err(gc->parent, "error setting gpio%d (%s) on EC: %d\n", gpio, name, ret);
}

static int cros_ec_gpio_get(struct gpio_chip *gc, unsigned int gpio)
{
	const char *name = gc->names[gpio] + strlen(cros_ec_gpio_prefix);
	struct cros_ec_device *cros_ec = gpiochip_get_data(gc);
	struct ec_params_gpio_get params;
	struct ec_response_gpio_get response;
	int ret;
	ssize_t copied;

	copied = strscpy(params.name, name, sizeof(params.name));
	if (copied < 0)
		return -EINVAL;

	ret = cros_ec_cmd(cros_ec, 0, EC_CMD_GPIO_GET, &params,
			  sizeof(params), &response, sizeof(response));
	if (ret < 0) {
		dev_err(gc->parent, "error getting gpio%d (%s) on EC: %d\n", gpio, name, ret);
		return ret;
	}

	return response.val;
}

#define CROS_EC_GPIO_INPUT         BIT(8)
#define CROS_EC_GPIO_OUTPUT        BIT(9)

static int cros_ec_gpio_get_direction(struct gpio_chip *gc, unsigned int gpio)
{
	const char *name = gc->names[gpio] + strlen(cros_ec_gpio_prefix);
	struct cros_ec_device *cros_ec = gpiochip_get_data(gc);
	struct ec_params_gpio_get_v1 params = {
		.subcmd = EC_GPIO_GET_INFO,
		.get_info.index = gpio,
	};
	struct ec_response_gpio_get_v1 response;
	int ret;

	ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GPIO_GET, &params,
			  sizeof(params), &response, sizeof(response));
	if (ret < 0) {
		dev_err(gc->parent, "error getting direction of gpio%d (%s) on EC: %d\n", gpio, name, ret);
		return ret;
	}

	if (response.get_info.flags & CROS_EC_GPIO_INPUT)
		return GPIO_LINE_DIRECTION_IN;

	if (response.get_info.flags & CROS_EC_GPIO_OUTPUT)
		return GPIO_LINE_DIRECTION_OUT;

	return -EINVAL;
}

/* Query EC for all gpio line names */
static int cros_ec_gpio_init_names(struct cros_ec_device *cros_ec, struct gpio_chip *gc)
{
	struct ec_params_gpio_get_v1 params = {
		.subcmd = EC_GPIO_GET_INFO,
	};
	struct ec_response_gpio_get_v1 response;
	int ret, i;
	/* EC may not NUL terminate */
	size_t name_len = strlen(cros_ec_gpio_prefix) + sizeof(response.get_info.name) + 1;
	ssize_t copied;
	const char **names;
	char *str;

	names = devm_kcalloc(gc->parent, gc->ngpio, sizeof(*names), GFP_KERNEL);
	if (!names)
		return -ENOMEM;
	gc->names = names;

	str = devm_kcalloc(gc->parent, gc->ngpio, name_len, GFP_KERNEL);
	if (!str)
		return -ENOMEM;

	/* Get gpio line names one at a time */
	for (i = 0; i < gc->ngpio; i++) {
		params.get_info.index = i;
		ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GPIO_GET, &params,
				  sizeof(params), &response, sizeof(response));
		if (ret < 0) {
			dev_err_probe(gc->parent, ret, "error getting gpio%d info\n", i);
			return ret;
		}

		names[i] = str;
		copied = scnprintf(str, name_len, "%s%s", cros_ec_gpio_prefix,
				   response.get_info.name);
		if (copied < 0)
			return copied;

		str += copied + 1;
	}

	return 0;
}

/* Query EC for number of gpios */
static int cros_ec_gpio_ngpios(struct cros_ec_device *cros_ec)
{
	struct ec_params_gpio_get_v1 params = {
		.subcmd = EC_GPIO_GET_COUNT,
	};
	struct ec_response_gpio_get_v1 response;
	int ret;

	ret = cros_ec_cmd(cros_ec, 1, EC_CMD_GPIO_GET, &params,
			  sizeof(params), &response, sizeof(response));
	if (ret < 0)
		return ret;

	return response.get_count.val;
}

static int cros_ec_gpio_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct device *parent = dev->parent;
	struct cros_ec_dev *ec_dev = dev_get_drvdata(parent);
	struct cros_ec_device *cros_ec = ec_dev->ec_dev;
	struct gpio_chip *gc;
	int ngpios;
	int ret;

	/* Use the fwnode from the protocol device, e.g. cros-ec-spi */
	device_set_node(dev, dev_fwnode(cros_ec->dev));

	ngpios = cros_ec_gpio_ngpios(cros_ec);
	if (ngpios < 0) {
		dev_err_probe(dev, ngpios, "error getting gpio count\n");
		return ngpios;
	}

	gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
	if (!gc)
		return -ENOMEM;

	gc->ngpio = ngpios;
	gc->parent = dev;
	ret = cros_ec_gpio_init_names(cros_ec, gc);
	if (ret)
		return ret;

	gc->can_sleep = true;
	gc->label = dev_name(dev);
	gc->base = -1;
	gc->set = cros_ec_gpio_set;
	gc->get = cros_ec_gpio_get;
	gc->get_direction = cros_ec_gpio_get_direction;

	return devm_gpiochip_add_data(dev, gc, cros_ec);
}

static const struct platform_device_id cros_ec_gpio_id[] = {
	{ "cros-ec-gpio", 0 },
	{}
};
MODULE_DEVICE_TABLE(platform, cros_ec_gpio_id);

static struct platform_driver cros_ec_gpio_driver = {
	.probe = cros_ec_gpio_probe,
	.driver = {
		.name = "cros-ec-gpio",
	},
	.id_table = cros_ec_gpio_id,
};
module_platform_driver(cros_ec_gpio_driver);

MODULE_DESCRIPTION("ChromeOS EC GPIO Driver");
MODULE_LICENSE("GPL");