OpenWrt – Blame information for rev 4
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
4 | office | 1 | /* |
2 | * Custom GPIO-based SPI driver |
||
3 | * |
||
4 | * Copyright (C) 2013 Marco Burato <zmaster.adsl@gmail.com> |
||
5 | * |
||
6 | * This program is free software; you can redistribute it and/or modify |
||
7 | * it under the terms of the GNU General Public License version 2 as |
||
8 | * published by the Free Software Foundation. |
||
9 | * |
||
10 | * Based on i2c-gpio-custom by: |
||
11 | * Copyright (C) 2007-2008 Gabor Juhos <juhosg@openwrt.org> |
||
12 | * --------------------------------------------------------------------------- |
||
13 | * |
||
14 | * The behaviour of this driver can be altered by setting some parameters |
||
15 | * from the insmod command line. |
||
16 | * |
||
17 | * The following parameters are adjustable: |
||
18 | * |
||
19 | * bus0 These four arguments can be arrays of |
||
20 | * bus1 1-8 unsigned integers as follows: |
||
21 | * bus2 |
||
22 | * bus3 <id>,<sck>,<mosi>,<miso>,<mode1>,<maxfreq1>,<cs1>,... |
||
23 | * |
||
24 | * where: |
||
25 | * |
||
26 | * <id> ID to used as device_id for the corresponding bus (required) |
||
27 | * <sck> GPIO pin ID to be used for bus SCK (required) |
||
28 | * <mosi> GPIO pin ID to be used for bus MOSI (required*) |
||
29 | * <miso> GPIO pin ID to be used for bus MISO (required*) |
||
30 | * <modeX> Mode configuration for slave X in the bus (required) |
||
31 | * (see /include/linux/spi/spi.h) |
||
32 | * <maxfreqX> Maximum clock frequency in Hz for slave X in the bus (required) |
||
33 | * <csX> GPIO pin ID to be used for slave X CS (required**) |
||
34 | * |
||
35 | * Notes: |
||
36 | * * If a signal is not used (for example there is no MISO) you need |
||
37 | * to set the GPIO pin ID for that signal to an invalid value. |
||
38 | * ** If you only have 1 slave in the bus with no CS, you can omit the |
||
39 | * <cs1> param or set it to an invalid GPIO id to disable it. When |
||
40 | * you have 2 or more slaves, they must all have a valid CS. |
||
41 | * |
||
42 | * If this driver is built into the kernel, you can use the following kernel |
||
43 | * command line parameters, with the same values as the corresponding module |
||
44 | * parameters listed above: |
||
45 | * |
||
46 | * spi-gpio-custom.bus0 |
||
47 | * spi-gpio-custom.bus1 |
||
48 | * spi-gpio-custom.bus2 |
||
49 | * spi-gpio-custom.bus3 |
||
50 | */ |
||
51 | |||
52 | #include <linux/kernel.h> |
||
53 | #include <linux/module.h> |
||
54 | #include <linux/init.h> |
||
55 | #include <linux/platform_device.h> |
||
56 | |||
57 | #include <linux/gpio.h> |
||
58 | #include <linux/spi/spi.h> |
||
59 | #include <linux/spi/spi_gpio.h> |
||
60 | |||
61 | #define DRV_NAME "spi-gpio-custom" |
||
62 | #define DRV_DESC "Custom GPIO-based SPI driver" |
||
63 | #define DRV_VERSION "0.1" |
||
64 | |||
65 | #define PFX DRV_NAME ": " |
||
66 | |||
67 | #define BUS_PARAM_ID 0 |
||
68 | #define BUS_PARAM_SCK 1 |
||
69 | #define BUS_PARAM_MOSI 2 |
||
70 | #define BUS_PARAM_MISO 3 |
||
71 | #define BUS_PARAM_MODE1 4 |
||
72 | #define BUS_PARAM_MAXFREQ1 5 |
||
73 | #define BUS_PARAM_CS1 6 |
||
74 | |||
75 | #define BUS_SLAVE_COUNT_MAX 8 |
||
76 | #define BUS_PARAM_REQUIRED 6 |
||
77 | #define BUS_PARAM_PER_SLAVE 3 |
||
78 | #define BUS_PARAM_COUNT (4+BUS_PARAM_PER_SLAVE*BUS_SLAVE_COUNT_MAX) |
||
79 | #define BUS_COUNT_MAX 4 |
||
80 | |||
81 | static unsigned int bus0[BUS_PARAM_COUNT] __initdata; |
||
82 | static unsigned int bus1[BUS_PARAM_COUNT] __initdata; |
||
83 | static unsigned int bus2[BUS_PARAM_COUNT] __initdata; |
||
84 | static unsigned int bus3[BUS_PARAM_COUNT] __initdata; |
||
85 | |||
86 | static unsigned int bus_nump[BUS_COUNT_MAX] __initdata; |
||
87 | |||
88 | #define BUS_PARM_DESC \ |
||
89 | " config -> id,sck,mosi,miso,mode1,maxfreq1[,cs1,mode2,maxfreq2,cs2,...]" |
||
90 | |||
91 | module_param_array(bus0, uint, &bus_nump[0], 0); |
||
92 | MODULE_PARM_DESC(bus0, "bus0" BUS_PARM_DESC); |
||
93 | module_param_array(bus1, uint, &bus_nump[1], 0); |
||
94 | MODULE_PARM_DESC(bus1, "bus1" BUS_PARM_DESC); |
||
95 | module_param_array(bus2, uint, &bus_nump[2], 0); |
||
96 | MODULE_PARM_DESC(bus2, "bus2" BUS_PARM_DESC); |
||
97 | module_param_array(bus3, uint, &bus_nump[3], 0); |
||
98 | MODULE_PARM_DESC(bus3, "bus3" BUS_PARM_DESC); |
||
99 | |||
100 | static struct platform_device *devices[BUS_COUNT_MAX]; |
||
101 | static unsigned int nr_devices; |
||
102 | |||
103 | static void spi_gpio_custom_cleanup(void) |
||
104 | { |
||
105 | int i; |
||
106 | |||
107 | for (i = 0; i < nr_devices; i++) |
||
108 | if (devices[i]) |
||
109 | platform_device_unregister(devices[i]); |
||
110 | } |
||
111 | |||
112 | static int __init spi_gpio_custom_get_slave_mode(unsigned int id, |
||
113 | unsigned int *params, |
||
114 | int slave_index) |
||
115 | { |
||
116 | int param_index; |
||
117 | |||
118 | param_index = BUS_PARAM_MODE1+slave_index*BUS_PARAM_PER_SLAVE; |
||
119 | if (param_index >= bus_nump[id]) |
||
120 | return -1; |
||
121 | |||
122 | return params[param_index]; |
||
123 | } |
||
124 | static int __init spi_gpio_custom_get_slave_maxfreq(unsigned int id, |
||
125 | unsigned int *params, |
||
126 | int slave_index) |
||
127 | { |
||
128 | int param_index; |
||
129 | |||
130 | param_index = BUS_PARAM_MAXFREQ1+slave_index*BUS_PARAM_PER_SLAVE; |
||
131 | if (param_index >= bus_nump[id]) |
||
132 | return -1; |
||
133 | |||
134 | return params[param_index]; |
||
135 | } |
||
136 | static int __init spi_gpio_custom_get_slave_cs(unsigned int id, |
||
137 | unsigned int *params, |
||
138 | int slave_index) |
||
139 | { |
||
140 | int param_index; |
||
141 | |||
142 | param_index = BUS_PARAM_CS1+slave_index*BUS_PARAM_PER_SLAVE; |
||
143 | if (param_index >= bus_nump[id]) |
||
144 | return -1; |
||
145 | if (!gpio_is_valid(params[param_index])) |
||
146 | return -1; |
||
147 | |||
148 | return params[param_index]; |
||
149 | } |
||
150 | |||
151 | static int __init spi_gpio_custom_check_params(unsigned int id, unsigned int *params) |
||
152 | { |
||
153 | int i; |
||
154 | struct spi_master *master; |
||
155 | |||
156 | if (bus_nump[id] < BUS_PARAM_REQUIRED) { |
||
157 | printk(KERN_ERR PFX "not enough values for parameter bus%d\n", |
||
158 | id); |
||
159 | return -EINVAL; |
||
160 | } |
||
161 | |||
162 | if (bus_nump[id] > (1+BUS_PARAM_CS1)) { |
||
163 | /* more than 1 device: check CS GPIOs */ |
||
164 | for (i = 0; i < BUS_SLAVE_COUNT_MAX; i++) { |
||
165 | /* no more slaves? */ |
||
166 | if (spi_gpio_custom_get_slave_mode(id, params, i) < 0) |
||
167 | break; |
||
168 | |||
169 | if (spi_gpio_custom_get_slave_cs(id, params, i) < 0) { |
||
170 | printk(KERN_ERR PFX "invalid/missing CS gpio for slave %d on bus %d\n", |
||
171 | i, params[BUS_PARAM_ID]); |
||
172 | return -EINVAL; |
||
173 | } |
||
174 | } |
||
175 | } |
||
176 | |||
177 | if (!gpio_is_valid(params[BUS_PARAM_SCK])) { |
||
178 | printk(KERN_ERR PFX "invalid SCK gpio for bus %d\n", |
||
179 | params[BUS_PARAM_ID]); |
||
180 | return -EINVAL; |
||
181 | } |
||
182 | |||
183 | master = spi_busnum_to_master(params[BUS_PARAM_ID]); |
||
184 | if (master) { |
||
185 | spi_master_put(master); |
||
186 | printk(KERN_ERR PFX "bus %d already exists\n", |
||
187 | params[BUS_PARAM_ID]); |
||
188 | return -EEXIST; |
||
189 | } |
||
190 | |||
191 | return 0; |
||
192 | } |
||
193 | |||
194 | static int __init spi_gpio_custom_add_one(unsigned int id, unsigned int *params) |
||
195 | { |
||
196 | struct platform_device *pdev; |
||
197 | struct spi_gpio_platform_data pdata; |
||
198 | int i; |
||
199 | int num_cs; |
||
200 | int err; |
||
201 | struct spi_master *master; |
||
202 | struct spi_device *slave; |
||
203 | struct spi_board_info slave_info; |
||
204 | int mode, maxfreq, cs; |
||
205 | |||
206 | |||
207 | if (!bus_nump[id]) |
||
208 | return 0; |
||
209 | |||
210 | err = spi_gpio_custom_check_params(id, params); |
||
211 | if (err) |
||
212 | goto err; |
||
213 | |||
214 | /* Create BUS device node */ |
||
215 | |||
216 | pdev = platform_device_alloc("spi_gpio", params[BUS_PARAM_ID]); |
||
217 | if (!pdev) { |
||
218 | err = -ENOMEM; |
||
219 | goto err; |
||
220 | } |
||
221 | |||
222 | num_cs = 0; |
||
223 | for (i = 0; i < BUS_SLAVE_COUNT_MAX; i++) { |
||
224 | /* no more slaves? */ |
||
225 | if (spi_gpio_custom_get_slave_mode(id, params, i) < 0) |
||
226 | break; |
||
227 | |||
228 | if (spi_gpio_custom_get_slave_cs(id, params, i) >= 0) |
||
229 | num_cs++; |
||
230 | } |
||
231 | if (num_cs == 0) { |
||
232 | /* |
||
233 | * Even if no CS is used, spi modules expect |
||
234 | * at least 1 (unused) |
||
235 | */ |
||
236 | num_cs = 1; |
||
237 | } |
||
238 | |||
239 | pdata.sck = params[BUS_PARAM_SCK]; |
||
240 | pdata.mosi = gpio_is_valid(params[BUS_PARAM_MOSI]) |
||
241 | ? params[BUS_PARAM_MOSI] |
||
242 | : SPI_GPIO_NO_MOSI; |
||
243 | pdata.miso = gpio_is_valid(params[BUS_PARAM_MISO]) |
||
244 | ? params[BUS_PARAM_MISO] |
||
245 | : SPI_GPIO_NO_MISO; |
||
246 | pdata.num_chipselect = num_cs; |
||
247 | |||
248 | err = platform_device_add_data(pdev, &pdata, sizeof(pdata)); |
||
249 | if (err) { |
||
250 | platform_device_put(pdev); |
||
251 | goto err; |
||
252 | } |
||
253 | |||
254 | err = platform_device_add(pdev); |
||
255 | if (err) { |
||
256 | printk(KERN_ERR PFX "platform_device_add failed with return code %d\n", |
||
257 | err); |
||
258 | platform_device_put(pdev); |
||
259 | goto err; |
||
260 | } |
||
261 | |||
262 | /* Register SLAVE devices */ |
||
263 | |||
264 | for (i = 0; i < BUS_SLAVE_COUNT_MAX; i++) { |
||
265 | mode = spi_gpio_custom_get_slave_mode(id, params, i); |
||
266 | maxfreq = spi_gpio_custom_get_slave_maxfreq(id, params, i); |
||
267 | cs = spi_gpio_custom_get_slave_cs(id, params, i); |
||
268 | |||
269 | /* no more slaves? */ |
||
270 | if (mode < 0) |
||
271 | break; |
||
272 | |||
273 | memset(&slave_info, 0, sizeof(slave_info)); |
||
274 | strcpy(slave_info.modalias, "spidev"); |
||
275 | slave_info.controller_data = (void *)((cs >= 0) |
||
276 | ? cs |
||
277 | : SPI_GPIO_NO_CHIPSELECT); |
||
278 | slave_info.max_speed_hz = maxfreq; |
||
279 | slave_info.bus_num = params[BUS_PARAM_ID]; |
||
280 | slave_info.chip_select = i; |
||
281 | slave_info.mode = mode; |
||
282 | |||
283 | master = spi_busnum_to_master(params[BUS_PARAM_ID]); |
||
284 | if (!master) { |
||
285 | printk(KERN_ERR PFX "unable to get master for bus %d\n", |
||
286 | params[BUS_PARAM_ID]); |
||
287 | err = -EINVAL; |
||
288 | goto err_unregister; |
||
289 | } |
||
290 | slave = spi_new_device(master, &slave_info); |
||
291 | spi_master_put(master); |
||
292 | if (!slave) { |
||
293 | printk(KERN_ERR PFX "unable to create slave %d for bus %d\n", |
||
294 | i, params[BUS_PARAM_ID]); |
||
295 | /* Will most likely fail due to unsupported mode bits */ |
||
296 | err = -EINVAL; |
||
297 | goto err_unregister; |
||
298 | } |
||
299 | } |
||
300 | |||
301 | devices[nr_devices++] = pdev; |
||
302 | |||
303 | return 0; |
||
304 | |||
305 | err_unregister: |
||
306 | platform_device_unregister(pdev); |
||
307 | err: |
||
308 | return err; |
||
309 | } |
||
310 | |||
311 | static int __init spi_gpio_custom_probe(void) |
||
312 | { |
||
313 | int err; |
||
314 | |||
315 | printk(KERN_INFO DRV_DESC " version " DRV_VERSION "\n"); |
||
316 | |||
317 | err = spi_gpio_custom_add_one(0, bus0); |
||
318 | if (err) |
||
319 | goto err; |
||
320 | |||
321 | err = spi_gpio_custom_add_one(1, bus1); |
||
322 | if (err) |
||
323 | goto err; |
||
324 | |||
325 | err = spi_gpio_custom_add_one(2, bus2); |
||
326 | if (err) |
||
327 | goto err; |
||
328 | |||
329 | err = spi_gpio_custom_add_one(3, bus3); |
||
330 | if (err) |
||
331 | goto err; |
||
332 | |||
333 | if (!nr_devices) { |
||
334 | printk(KERN_ERR PFX "no bus parameter(s) specified\n"); |
||
335 | err = -ENODEV; |
||
336 | goto err; |
||
337 | } |
||
338 | |||
339 | return 0; |
||
340 | |||
341 | err: |
||
342 | spi_gpio_custom_cleanup(); |
||
343 | return err; |
||
344 | } |
||
345 | |||
346 | #ifdef MODULE |
||
347 | static int __init spi_gpio_custom_init(void) |
||
348 | { |
||
349 | return spi_gpio_custom_probe(); |
||
350 | } |
||
351 | module_init(spi_gpio_custom_init); |
||
352 | |||
353 | static void __exit spi_gpio_custom_exit(void) |
||
354 | { |
||
355 | spi_gpio_custom_cleanup(); |
||
356 | } |
||
357 | module_exit(spi_gpio_custom_exit); |
||
358 | #else |
||
359 | subsys_initcall(spi_gpio_custom_probe); |
||
360 | #endif /* MODULE*/ |
||
361 | |||
362 | MODULE_LICENSE("GPL v2"); |
||
363 | MODULE_AUTHOR("Marco Burato <zmaster.adsl@gmail.com>"); |
||
364 | MODULE_DESCRIPTION(DRV_DESC); |
||
365 | MODULE_VERSION(DRV_VERSION); |