1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Support Infineon TLE62x0 driver chips |
4 | * |
5 | * Copyright (c) 2007 Simtec Electronics |
6 | * Ben Dooks, <ben@simtec.co.uk> |
7 | */ |
8 | |
9 | #include <linux/device.h> |
10 | #include <linux/kernel.h> |
11 | #include <linux/module.h> |
12 | #include <linux/slab.h> |
13 | |
14 | #include <linux/spi/spi.h> |
15 | #include <linux/spi/tle62x0.h> |
16 | |
17 | |
18 | #define CMD_READ 0x00 |
19 | #define CMD_SET 0xff |
20 | |
21 | #define DIAG_NORMAL 0x03 |
22 | #define DIAG_OVERLOAD 0x02 |
23 | #define DIAG_OPEN 0x01 |
24 | #define DIAG_SHORTGND 0x00 |
25 | |
26 | struct tle62x0_state { |
27 | struct spi_device *us; |
28 | struct mutex lock; |
29 | unsigned int nr_gpio; |
30 | unsigned int gpio_state; |
31 | |
32 | unsigned char tx_buff[4]; |
33 | unsigned char rx_buff[4]; |
34 | }; |
35 | |
36 | static int to_gpio_num(struct device_attribute *attr); |
37 | |
38 | static inline int tle62x0_write(struct tle62x0_state *st) |
39 | { |
40 | unsigned char *buff = st->tx_buff; |
41 | unsigned int gpio_state = st->gpio_state; |
42 | |
43 | buff[0] = CMD_SET; |
44 | |
45 | if (st->nr_gpio == 16) { |
46 | buff[1] = gpio_state >> 8; |
47 | buff[2] = gpio_state; |
48 | } else { |
49 | buff[1] = gpio_state; |
50 | } |
51 | |
52 | dev_dbg(&st->us->dev, "buff %3ph\n" , buff); |
53 | |
54 | return spi_write(spi: st->us, buf: buff, len: (st->nr_gpio == 16) ? 3 : 2); |
55 | } |
56 | |
57 | static inline int tle62x0_read(struct tle62x0_state *st) |
58 | { |
59 | unsigned char *txbuff = st->tx_buff; |
60 | struct spi_transfer xfer = { |
61 | .tx_buf = txbuff, |
62 | .rx_buf = st->rx_buff, |
63 | .len = (st->nr_gpio * 2) / 8, |
64 | }; |
65 | struct spi_message msg; |
66 | |
67 | txbuff[0] = CMD_READ; |
68 | txbuff[1] = 0x00; |
69 | txbuff[2] = 0x00; |
70 | txbuff[3] = 0x00; |
71 | |
72 | spi_message_init(m: &msg); |
73 | spi_message_add_tail(t: &xfer, m: &msg); |
74 | |
75 | return spi_sync(spi: st->us, message: &msg); |
76 | } |
77 | |
78 | static unsigned char *decode_fault(unsigned int fault_code) |
79 | { |
80 | fault_code &= 3; |
81 | |
82 | switch (fault_code) { |
83 | case DIAG_NORMAL: |
84 | return "N" ; |
85 | case DIAG_OVERLOAD: |
86 | return "V" ; |
87 | case DIAG_OPEN: |
88 | return "O" ; |
89 | case DIAG_SHORTGND: |
90 | return "G" ; |
91 | } |
92 | |
93 | return "?" ; |
94 | } |
95 | |
96 | static ssize_t tle62x0_status_show(struct device *dev, |
97 | struct device_attribute *attr, char *buf) |
98 | { |
99 | struct tle62x0_state *st = dev_get_drvdata(dev); |
100 | char *bp = buf; |
101 | unsigned char *buff = st->rx_buff; |
102 | unsigned long fault = 0; |
103 | int ptr; |
104 | int ret; |
105 | |
106 | mutex_lock(&st->lock); |
107 | ret = tle62x0_read(st); |
108 | dev_dbg(dev, "tle62x0_read() returned %d\n" , ret); |
109 | if (ret < 0) { |
110 | mutex_unlock(lock: &st->lock); |
111 | return ret; |
112 | } |
113 | |
114 | for (ptr = 0; ptr < (st->nr_gpio * 2)/8; ptr += 1) { |
115 | fault <<= 8; |
116 | fault |= ((unsigned long)buff[ptr]); |
117 | |
118 | dev_dbg(dev, "byte %d is %02x\n" , ptr, buff[ptr]); |
119 | } |
120 | |
121 | for (ptr = 0; ptr < st->nr_gpio; ptr++) { |
122 | bp += sprintf(buf: bp, fmt: "%s " , decode_fault(fault_code: fault >> (ptr * 2))); |
123 | } |
124 | |
125 | *bp++ = '\n'; |
126 | |
127 | mutex_unlock(lock: &st->lock); |
128 | return bp - buf; |
129 | } |
130 | |
131 | static DEVICE_ATTR(status_show, S_IRUGO, tle62x0_status_show, NULL); |
132 | |
133 | static ssize_t tle62x0_gpio_show(struct device *dev, |
134 | struct device_attribute *attr, char *buf) |
135 | { |
136 | struct tle62x0_state *st = dev_get_drvdata(dev); |
137 | int gpio_num = to_gpio_num(attr); |
138 | int value; |
139 | |
140 | mutex_lock(&st->lock); |
141 | value = (st->gpio_state >> gpio_num) & 1; |
142 | mutex_unlock(lock: &st->lock); |
143 | |
144 | return sysfs_emit(buf, fmt: "%d" , value); |
145 | } |
146 | |
147 | static ssize_t tle62x0_gpio_store(struct device *dev, |
148 | struct device_attribute *attr, |
149 | const char *buf, size_t len) |
150 | { |
151 | struct tle62x0_state *st = dev_get_drvdata(dev); |
152 | int gpio_num = to_gpio_num(attr); |
153 | unsigned long val; |
154 | char *endp; |
155 | |
156 | val = simple_strtoul(buf, &endp, 0); |
157 | if (buf == endp) |
158 | return -EINVAL; |
159 | |
160 | dev_dbg(dev, "setting gpio %d to %ld\n" , gpio_num, val); |
161 | |
162 | mutex_lock(&st->lock); |
163 | |
164 | if (val) |
165 | st->gpio_state |= 1 << gpio_num; |
166 | else |
167 | st->gpio_state &= ~(1 << gpio_num); |
168 | |
169 | tle62x0_write(st); |
170 | mutex_unlock(lock: &st->lock); |
171 | |
172 | return len; |
173 | } |
174 | |
175 | static DEVICE_ATTR(gpio1, S_IWUSR|S_IRUGO, |
176 | tle62x0_gpio_show, tle62x0_gpio_store); |
177 | static DEVICE_ATTR(gpio2, S_IWUSR|S_IRUGO, |
178 | tle62x0_gpio_show, tle62x0_gpio_store); |
179 | static DEVICE_ATTR(gpio3, S_IWUSR|S_IRUGO, |
180 | tle62x0_gpio_show, tle62x0_gpio_store); |
181 | static DEVICE_ATTR(gpio4, S_IWUSR|S_IRUGO, |
182 | tle62x0_gpio_show, tle62x0_gpio_store); |
183 | static DEVICE_ATTR(gpio5, S_IWUSR|S_IRUGO, |
184 | tle62x0_gpio_show, tle62x0_gpio_store); |
185 | static DEVICE_ATTR(gpio6, S_IWUSR|S_IRUGO, |
186 | tle62x0_gpio_show, tle62x0_gpio_store); |
187 | static DEVICE_ATTR(gpio7, S_IWUSR|S_IRUGO, |
188 | tle62x0_gpio_show, tle62x0_gpio_store); |
189 | static DEVICE_ATTR(gpio8, S_IWUSR|S_IRUGO, |
190 | tle62x0_gpio_show, tle62x0_gpio_store); |
191 | static DEVICE_ATTR(gpio9, S_IWUSR|S_IRUGO, |
192 | tle62x0_gpio_show, tle62x0_gpio_store); |
193 | static DEVICE_ATTR(gpio10, S_IWUSR|S_IRUGO, |
194 | tle62x0_gpio_show, tle62x0_gpio_store); |
195 | static DEVICE_ATTR(gpio11, S_IWUSR|S_IRUGO, |
196 | tle62x0_gpio_show, tle62x0_gpio_store); |
197 | static DEVICE_ATTR(gpio12, S_IWUSR|S_IRUGO, |
198 | tle62x0_gpio_show, tle62x0_gpio_store); |
199 | static DEVICE_ATTR(gpio13, S_IWUSR|S_IRUGO, |
200 | tle62x0_gpio_show, tle62x0_gpio_store); |
201 | static DEVICE_ATTR(gpio14, S_IWUSR|S_IRUGO, |
202 | tle62x0_gpio_show, tle62x0_gpio_store); |
203 | static DEVICE_ATTR(gpio15, S_IWUSR|S_IRUGO, |
204 | tle62x0_gpio_show, tle62x0_gpio_store); |
205 | static DEVICE_ATTR(gpio16, S_IWUSR|S_IRUGO, |
206 | tle62x0_gpio_show, tle62x0_gpio_store); |
207 | |
208 | static struct device_attribute *gpio_attrs[] = { |
209 | [0] = &dev_attr_gpio1, |
210 | [1] = &dev_attr_gpio2, |
211 | [2] = &dev_attr_gpio3, |
212 | [3] = &dev_attr_gpio4, |
213 | [4] = &dev_attr_gpio5, |
214 | [5] = &dev_attr_gpio6, |
215 | [6] = &dev_attr_gpio7, |
216 | [7] = &dev_attr_gpio8, |
217 | [8] = &dev_attr_gpio9, |
218 | [9] = &dev_attr_gpio10, |
219 | [10] = &dev_attr_gpio11, |
220 | [11] = &dev_attr_gpio12, |
221 | [12] = &dev_attr_gpio13, |
222 | [13] = &dev_attr_gpio14, |
223 | [14] = &dev_attr_gpio15, |
224 | [15] = &dev_attr_gpio16 |
225 | }; |
226 | |
227 | static int to_gpio_num(struct device_attribute *attr) |
228 | { |
229 | int ptr; |
230 | |
231 | for (ptr = 0; ptr < ARRAY_SIZE(gpio_attrs); ptr++) { |
232 | if (gpio_attrs[ptr] == attr) |
233 | return ptr; |
234 | } |
235 | |
236 | return -1; |
237 | } |
238 | |
239 | static int tle62x0_probe(struct spi_device *spi) |
240 | { |
241 | struct tle62x0_state *st; |
242 | struct tle62x0_pdata *pdata; |
243 | int ptr; |
244 | int ret; |
245 | |
246 | pdata = dev_get_platdata(dev: &spi->dev); |
247 | if (pdata == NULL) { |
248 | dev_err(&spi->dev, "no device data specified\n" ); |
249 | return -EINVAL; |
250 | } |
251 | |
252 | st = kzalloc(size: sizeof(struct tle62x0_state), GFP_KERNEL); |
253 | if (st == NULL) |
254 | return -ENOMEM; |
255 | |
256 | st->us = spi; |
257 | st->nr_gpio = pdata->gpio_count; |
258 | st->gpio_state = pdata->init_state; |
259 | |
260 | mutex_init(&st->lock); |
261 | |
262 | ret = device_create_file(device: &spi->dev, entry: &dev_attr_status_show); |
263 | if (ret) { |
264 | dev_err(&spi->dev, "cannot create status attribute\n" ); |
265 | goto err_status; |
266 | } |
267 | |
268 | for (ptr = 0; ptr < pdata->gpio_count; ptr++) { |
269 | ret = device_create_file(device: &spi->dev, entry: gpio_attrs[ptr]); |
270 | if (ret) { |
271 | dev_err(&spi->dev, "cannot create gpio attribute\n" ); |
272 | goto err_gpios; |
273 | } |
274 | } |
275 | |
276 | /* tle62x0_write(st); */ |
277 | spi_set_drvdata(spi, data: st); |
278 | return 0; |
279 | |
280 | err_gpios: |
281 | while (--ptr >= 0) |
282 | device_remove_file(dev: &spi->dev, attr: gpio_attrs[ptr]); |
283 | |
284 | device_remove_file(dev: &spi->dev, attr: &dev_attr_status_show); |
285 | |
286 | err_status: |
287 | kfree(objp: st); |
288 | return ret; |
289 | } |
290 | |
291 | static void tle62x0_remove(struct spi_device *spi) |
292 | { |
293 | struct tle62x0_state *st = spi_get_drvdata(spi); |
294 | int ptr; |
295 | |
296 | for (ptr = 0; ptr < st->nr_gpio; ptr++) |
297 | device_remove_file(dev: &spi->dev, attr: gpio_attrs[ptr]); |
298 | |
299 | device_remove_file(dev: &spi->dev, attr: &dev_attr_status_show); |
300 | kfree(objp: st); |
301 | } |
302 | |
303 | static struct spi_driver tle62x0_driver = { |
304 | .driver = { |
305 | .name = "tle62x0" , |
306 | }, |
307 | .probe = tle62x0_probe, |
308 | .remove = tle62x0_remove, |
309 | }; |
310 | |
311 | module_spi_driver(tle62x0_driver); |
312 | |
313 | MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>" ); |
314 | MODULE_DESCRIPTION("TLE62x0 SPI driver" ); |
315 | MODULE_LICENSE("GPL v2" ); |
316 | MODULE_ALIAS("spi:tle62x0" ); |
317 | |