diff options
author | David Brownell <dbrownell@users.sourceforge.net> | 2008-12-22 06:05:27 -0500 |
---|---|---|
committer | Samuel Ortiz <samuel@sortiz.org> | 2009-01-04 06:17:43 -0500 |
commit | 0931a4c6dbfab03f2bfd22a9170130f7b155d53a (patch) | |
tree | 3b943723615a0212bf5e5b74b1fbc0689ee6cbae /drivers/mfd/dm355evm_msp.c | |
parent | 4331bb32339a55fd88fbfb0581ed5132207bf9a2 (diff) |
mfd: dm355evm msp430 driver
Basic MFD framework for the MSP430 microcontroller firmware used
on the dm355evm board:
- Provides an interface for other drivers: register read/write
utilities, and register declarations.
- Directly exports:
* Many signals through the GPIO framework
+ LEDs
+ SW6 through gpio sysfs
+ NTSC/nPAL jumper through gpio sysfs
+ ... more could be added later, e.g. MMC signals
* Child devices:
+ LEDs, via leds-gpio child (and default triggers)
+ RTC, via rtc-dm355evm child device
+ Buttons and IR control, via dm355evm_keys
- Supports power-off system call. Use the reset button to power
the board back up; the power supply LED will be on, but the
MSP430 waits to re-activate the regulators.
- On probe() this:
* Announces firmware revision
* Turns off the banked LEDs
* Exports the resources noted above
* Hooks the power-off support
* Muxes tvp5146 -or- imager for video input
Unless the new tvp514x driver (tracked for mainline) is configured,
this assumes that some custom imager driver handles video-in.
This completely ignores the registers reporting the output voltages
on the various power supplies. Someone could add a hwmon interface
if that seems useful.
Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Samuel Ortiz <sameo@openedhand.com>
Diffstat (limited to 'drivers/mfd/dm355evm_msp.c')
-rw-r--r-- | drivers/mfd/dm355evm_msp.c | 420 |
1 files changed, 420 insertions, 0 deletions
diff --git a/drivers/mfd/dm355evm_msp.c b/drivers/mfd/dm355evm_msp.c new file mode 100644 index 000000000000..4214b3f72426 --- /dev/null +++ b/drivers/mfd/dm355evm_msp.c | |||
@@ -0,0 +1,420 @@ | |||
1 | /* | ||
2 | * dm355evm_msp.c - driver for MSP430 firmware on DM355EVM board | ||
3 | * | ||
4 | * Copyright (C) 2008 David Brownell | ||
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 as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/init.h> | ||
13 | #include <linux/mutex.h> | ||
14 | #include <linux/platform_device.h> | ||
15 | #include <linux/clk.h> | ||
16 | #include <linux/err.h> | ||
17 | #include <linux/gpio.h> | ||
18 | #include <linux/leds.h> | ||
19 | #include <linux/i2c.h> | ||
20 | #include <linux/i2c/dm355evm_msp.h> | ||
21 | |||
22 | |||
23 | /* | ||
24 | * The DM355 is a DaVinci chip with video support but no C64+ DSP. Its | ||
25 | * EVM board has an MSP430 programmed with firmware for various board | ||
26 | * support functions. This driver exposes some of them directly, and | ||
27 | * supports other drivers (e.g. RTC, input) for more complex access. | ||
28 | * | ||
29 | * Because this firmware is entirely board-specific, this file embeds | ||
30 | * knowledge that would be passed as platform_data in a generic driver. | ||
31 | * | ||
32 | * This driver was tested with firmware revision A4. | ||
33 | */ | ||
34 | |||
35 | #if defined(CONFIG_KEYBOARD_DM355EVM) \ | ||
36 | || defined(CONFIG_KEYBOARD_DM355EVM_MODULE) | ||
37 | #define msp_has_keyboard() true | ||
38 | #else | ||
39 | #define msp_has_keyboard() false | ||
40 | #endif | ||
41 | |||
42 | #if defined(CONFIG_LEDS_GPIO) || defined(CONFIG_LEDS_GPIO_MODULE) | ||
43 | #define msp_has_leds() true | ||
44 | #else | ||
45 | #define msp_has_leds() false | ||
46 | #endif | ||
47 | |||
48 | #if defined(CONFIG_RTC_DRV_DM355EVM) || defined(CONFIG_RTC_DRV_DM355EVM_MODULE) | ||
49 | #define msp_has_rtc() true | ||
50 | #else | ||
51 | #define msp_has_rtc() false | ||
52 | #endif | ||
53 | |||
54 | #if defined(CONFIG_VIDEO_TVP514X) || defined(CONFIG_VIDEO_TVP514X_MODULE) | ||
55 | #define msp_has_tvp() true | ||
56 | #else | ||
57 | #define msp_has_tvp() false | ||
58 | #endif | ||
59 | |||
60 | |||
61 | /*----------------------------------------------------------------------*/ | ||
62 | |||
63 | /* REVISIT for paranoia's sake, retry reads/writes on error */ | ||
64 | |||
65 | static struct i2c_client *msp430; | ||
66 | |||
67 | /** | ||
68 | * dm355evm_msp_write - Writes a register in dm355evm_msp | ||
69 | * @value: the value to be written | ||
70 | * @reg: register address | ||
71 | * | ||
72 | * Returns result of operation - 0 is success, else negative errno | ||
73 | */ | ||
74 | int dm355evm_msp_write(u8 value, u8 reg) | ||
75 | { | ||
76 | return i2c_smbus_write_byte_data(msp430, reg, value); | ||
77 | } | ||
78 | EXPORT_SYMBOL(dm355evm_msp_write); | ||
79 | |||
80 | /** | ||
81 | * dm355evm_msp_read - Reads a register from dm355evm_msp | ||
82 | * @reg: register address | ||
83 | * | ||
84 | * Returns result of operation - value, or negative errno | ||
85 | */ | ||
86 | int dm355evm_msp_read(u8 reg) | ||
87 | { | ||
88 | return i2c_smbus_read_byte_data(msp430, reg); | ||
89 | } | ||
90 | EXPORT_SYMBOL(dm355evm_msp_read); | ||
91 | |||
92 | /*----------------------------------------------------------------------*/ | ||
93 | |||
94 | /* | ||
95 | * Many of the msp430 pins are just used as fixed-direction GPIOs. | ||
96 | * We could export a few more of them this way, if we wanted. | ||
97 | */ | ||
98 | #define MSP_GPIO(bit,reg) ((DM355EVM_MSP_ ## reg) << 3 | (bit)) | ||
99 | |||
100 | static const u8 msp_gpios[] = { | ||
101 | /* eight leds */ | ||
102 | MSP_GPIO(0, LED), MSP_GPIO(1, LED), | ||
103 | MSP_GPIO(2, LED), MSP_GPIO(3, LED), | ||
104 | MSP_GPIO(4, LED), MSP_GPIO(5, LED), | ||
105 | MSP_GPIO(6, LED), MSP_GPIO(7, LED), | ||
106 | /* SW6 and the NTSC/nPAL jumper */ | ||
107 | MSP_GPIO(0, SWITCH1), MSP_GPIO(1, SWITCH1), | ||
108 | MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1), | ||
109 | MSP_GPIO(4, SWITCH1), | ||
110 | }; | ||
111 | |||
112 | #define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3) | ||
113 | #define MSP_GPIO_MASK(offset) BIT(msp_gpios[(offset)] & 0x07) | ||
114 | |||
115 | static int msp_gpio_in(struct gpio_chip *chip, unsigned offset) | ||
116 | { | ||
117 | switch (MSP_GPIO_REG(offset)) { | ||
118 | case DM355EVM_MSP_SWITCH1: | ||
119 | case DM355EVM_MSP_SWITCH2: | ||
120 | case DM355EVM_MSP_SDMMC: | ||
121 | return 0; | ||
122 | default: | ||
123 | return -EINVAL; | ||
124 | } | ||
125 | } | ||
126 | |||
127 | static u8 msp_led_cache; | ||
128 | |||
129 | static int msp_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
130 | { | ||
131 | int reg, status; | ||
132 | |||
133 | reg = MSP_GPIO_REG(offset); | ||
134 | status = dm355evm_msp_read(reg); | ||
135 | if (status < 0) | ||
136 | return status; | ||
137 | if (reg == DM355EVM_MSP_LED) | ||
138 | msp_led_cache = status; | ||
139 | return status & MSP_GPIO_MASK(offset); | ||
140 | } | ||
141 | |||
142 | static int msp_gpio_out(struct gpio_chip *chip, unsigned offset, int value) | ||
143 | { | ||
144 | int mask, bits; | ||
145 | |||
146 | /* NOTE: there are some other signals that could be | ||
147 | * packaged as output GPIOs, but they aren't as useful | ||
148 | * as the LEDs ... so for now we don't. | ||
149 | */ | ||
150 | if (MSP_GPIO_REG(offset) != DM355EVM_MSP_LED) | ||
151 | return -EINVAL; | ||
152 | |||
153 | mask = MSP_GPIO_MASK(offset); | ||
154 | bits = msp_led_cache; | ||
155 | |||
156 | bits &= ~mask; | ||
157 | if (value) | ||
158 | bits |= mask; | ||
159 | msp_led_cache = bits; | ||
160 | |||
161 | return dm355evm_msp_write(bits, DM355EVM_MSP_LED); | ||
162 | } | ||
163 | |||
164 | static void msp_gpio_set(struct gpio_chip *chip, unsigned offset, int value) | ||
165 | { | ||
166 | msp_gpio_out(chip, offset, value); | ||
167 | } | ||
168 | |||
169 | static struct gpio_chip dm355evm_msp_gpio = { | ||
170 | .label = "dm355evm_msp", | ||
171 | .owner = THIS_MODULE, | ||
172 | .direction_input = msp_gpio_in, | ||
173 | .get = msp_gpio_get, | ||
174 | .direction_output = msp_gpio_out, | ||
175 | .set = msp_gpio_set, | ||
176 | .base = -EINVAL, /* dynamic assignment */ | ||
177 | .ngpio = ARRAY_SIZE(msp_gpios), | ||
178 | .can_sleep = true, | ||
179 | }; | ||
180 | |||
181 | /*----------------------------------------------------------------------*/ | ||
182 | |||
183 | static struct device *add_child(struct i2c_client *client, const char *name, | ||
184 | void *pdata, unsigned pdata_len, | ||
185 | bool can_wakeup, int irq) | ||
186 | { | ||
187 | struct platform_device *pdev; | ||
188 | int status; | ||
189 | |||
190 | pdev = platform_device_alloc(name, -1); | ||
191 | if (!pdev) { | ||
192 | dev_dbg(&client->dev, "can't alloc dev\n"); | ||
193 | status = -ENOMEM; | ||
194 | goto err; | ||
195 | } | ||
196 | |||
197 | device_init_wakeup(&pdev->dev, can_wakeup); | ||
198 | pdev->dev.parent = &client->dev; | ||
199 | |||
200 | if (pdata) { | ||
201 | status = platform_device_add_data(pdev, pdata, pdata_len); | ||
202 | if (status < 0) { | ||
203 | dev_dbg(&pdev->dev, "can't add platform_data\n"); | ||
204 | goto err; | ||
205 | } | ||
206 | } | ||
207 | |||
208 | if (irq) { | ||
209 | struct resource r = { | ||
210 | .start = irq, | ||
211 | .flags = IORESOURCE_IRQ, | ||
212 | }; | ||
213 | |||
214 | status = platform_device_add_resources(pdev, &r, 1); | ||
215 | if (status < 0) { | ||
216 | dev_dbg(&pdev->dev, "can't add irq\n"); | ||
217 | goto err; | ||
218 | } | ||
219 | } | ||
220 | |||
221 | status = platform_device_add(pdev); | ||
222 | |||
223 | err: | ||
224 | if (status < 0) { | ||
225 | platform_device_put(pdev); | ||
226 | dev_err(&client->dev, "can't add %s dev\n", name); | ||
227 | return ERR_PTR(status); | ||
228 | } | ||
229 | return &pdev->dev; | ||
230 | } | ||
231 | |||
232 | static int add_children(struct i2c_client *client) | ||
233 | { | ||
234 | static const struct { | ||
235 | int offset; | ||
236 | char *label; | ||
237 | } config_inputs[] = { | ||
238 | /* 8 == right after the LEDs */ | ||
239 | { 8 + 0, "sw6_1", }, | ||
240 | { 8 + 1, "sw6_2", }, | ||
241 | { 8 + 2, "sw6_3", }, | ||
242 | { 8 + 3, "sw6_4", }, | ||
243 | { 8 + 4, "NTSC/nPAL", }, | ||
244 | }; | ||
245 | |||
246 | struct device *child; | ||
247 | int status; | ||
248 | int i; | ||
249 | |||
250 | /* GPIO-ish stuff */ | ||
251 | dm355evm_msp_gpio.dev = &client->dev; | ||
252 | status = gpiochip_add(&dm355evm_msp_gpio); | ||
253 | if (status < 0) | ||
254 | return status; | ||
255 | |||
256 | /* LED output */ | ||
257 | if (msp_has_leds()) { | ||
258 | #define GPIO_LED(l) .name = l, .active_low = true | ||
259 | static struct gpio_led evm_leds[] = { | ||
260 | { GPIO_LED("dm355evm::ds14"), | ||
261 | .default_trigger = "heartbeat", }, | ||
262 | { GPIO_LED("dm355evm::ds15"), | ||
263 | .default_trigger = "mmc0", }, | ||
264 | { GPIO_LED("dm355evm::ds16"), | ||
265 | /* could also be a CE-ATA drive */ | ||
266 | .default_trigger = "mmc1", }, | ||
267 | { GPIO_LED("dm355evm::ds17"), | ||
268 | .default_trigger = "nand-disk", }, | ||
269 | { GPIO_LED("dm355evm::ds18"), }, | ||
270 | { GPIO_LED("dm355evm::ds19"), }, | ||
271 | { GPIO_LED("dm355evm::ds20"), }, | ||
272 | { GPIO_LED("dm355evm::ds21"), }, | ||
273 | }; | ||
274 | #undef GPIO_LED | ||
275 | |||
276 | struct gpio_led_platform_data evm_led_data = { | ||
277 | .num_leds = ARRAY_SIZE(evm_leds), | ||
278 | .leds = evm_leds, | ||
279 | }; | ||
280 | |||
281 | for (i = 0; i < ARRAY_SIZE(evm_leds); i++) | ||
282 | evm_leds[i].gpio = i + dm355evm_msp_gpio.base; | ||
283 | |||
284 | /* NOTE: these are the only fully programmable LEDs | ||
285 | * on the board, since GPIO-61/ds22 (and many signals | ||
286 | * going to DC7) must be used for AEMIF address lines | ||
287 | * unless the top 1 GB of NAND is unused... | ||
288 | */ | ||
289 | child = add_child(client, "leds-gpio", | ||
290 | &evm_led_data, sizeof(evm_led_data), | ||
291 | false, 0); | ||
292 | if (IS_ERR(child)) | ||
293 | return PTR_ERR(child); | ||
294 | } | ||
295 | |||
296 | /* configuration inputs */ | ||
297 | for (i = 0; i < ARRAY_SIZE(config_inputs); i++) { | ||
298 | int gpio = dm355evm_msp_gpio.base + config_inputs[i].offset; | ||
299 | |||
300 | gpio_request(gpio, config_inputs[i].label); | ||
301 | gpio_direction_input(gpio); | ||
302 | |||
303 | /* make it easy for userspace to see these */ | ||
304 | gpio_export(gpio, false); | ||
305 | } | ||
306 | |||
307 | /* RTC is a 32 bit counter, no alarm */ | ||
308 | if (msp_has_rtc()) { | ||
309 | child = add_child(client, "rtc-dm355evm", | ||
310 | NULL, 0, false, 0); | ||
311 | if (IS_ERR(child)) | ||
312 | return PTR_ERR(child); | ||
313 | } | ||
314 | |||
315 | /* input from buttons and IR remote (uses the IRQ) */ | ||
316 | if (msp_has_keyboard()) { | ||
317 | child = add_child(client, "dm355evm_keys", | ||
318 | NULL, 0, true, client->irq); | ||
319 | if (IS_ERR(child)) | ||
320 | return PTR_ERR(child); | ||
321 | } | ||
322 | |||
323 | return 0; | ||
324 | } | ||
325 | |||
326 | /*----------------------------------------------------------------------*/ | ||
327 | |||
328 | static void dm355evm_command(unsigned command) | ||
329 | { | ||
330 | int status; | ||
331 | |||
332 | status = dm355evm_msp_write(command, DM355EVM_MSP_COMMAND); | ||
333 | if (status < 0) | ||
334 | dev_err(&msp430->dev, "command %d failure %d\n", | ||
335 | command, status); | ||
336 | } | ||
337 | |||
338 | static void dm355evm_power_off(void) | ||
339 | { | ||
340 | dm355evm_command(MSP_COMMAND_POWEROFF); | ||
341 | } | ||
342 | |||
343 | static int dm355evm_msp_remove(struct i2c_client *client) | ||
344 | { | ||
345 | pm_power_off = NULL; | ||
346 | msp430 = NULL; | ||
347 | return 0; | ||
348 | } | ||
349 | |||
350 | static int | ||
351 | dm355evm_msp_probe(struct i2c_client *client, const struct i2c_device_id *id) | ||
352 | { | ||
353 | int status; | ||
354 | const char *video = msp_has_tvp() ? "TVP5146" : "imager"; | ||
355 | |||
356 | if (msp430) | ||
357 | return -EBUSY; | ||
358 | msp430 = client; | ||
359 | |||
360 | /* display revision status; doubles as sanity check */ | ||
361 | status = dm355evm_msp_read(DM355EVM_MSP_FIRMREV); | ||
362 | if (status < 0) | ||
363 | goto fail; | ||
364 | dev_info(&client->dev, "firmware v.%02X, %s as video-in\n", | ||
365 | status, video); | ||
366 | |||
367 | /* mux video input: either tvp5146 or some external imager */ | ||
368 | status = dm355evm_msp_write(msp_has_tvp() ? 0 : MSP_VIDEO_IMAGER, | ||
369 | DM355EVM_MSP_VIDEO_IN); | ||
370 | if (status < 0) | ||
371 | dev_warn(&client->dev, "error %d muxing %s as video-in\n", | ||
372 | status, video); | ||
373 | |||
374 | /* init LED cache, and turn off the LEDs */ | ||
375 | msp_led_cache = 0xff; | ||
376 | dm355evm_msp_write(msp_led_cache, DM355EVM_MSP_LED); | ||
377 | |||
378 | /* export capabilities we support */ | ||
379 | status = add_children(client); | ||
380 | if (status < 0) | ||
381 | goto fail; | ||
382 | |||
383 | /* PM hookup */ | ||
384 | pm_power_off = dm355evm_power_off; | ||
385 | |||
386 | return 0; | ||
387 | |||
388 | fail: | ||
389 | /* FIXME remove children ... */ | ||
390 | dm355evm_msp_remove(client); | ||
391 | return status; | ||
392 | } | ||
393 | |||
394 | static const struct i2c_device_id dm355evm_msp_ids[] = { | ||
395 | { "dm355evm_msp", 0 }, | ||
396 | { /* end of list */ }, | ||
397 | }; | ||
398 | MODULE_DEVICE_TABLE(i2c, dm355evm_msp_ids); | ||
399 | |||
400 | static struct i2c_driver dm355evm_msp_driver = { | ||
401 | .driver.name = "dm355evm_msp", | ||
402 | .id_table = dm355evm_msp_ids, | ||
403 | .probe = dm355evm_msp_probe, | ||
404 | .remove = dm355evm_msp_remove, | ||
405 | }; | ||
406 | |||
407 | static int __init dm355evm_msp_init(void) | ||
408 | { | ||
409 | return i2c_add_driver(&dm355evm_msp_driver); | ||
410 | } | ||
411 | subsys_initcall(dm355evm_msp_init); | ||
412 | |||
413 | static void __exit dm355evm_msp_exit(void) | ||
414 | { | ||
415 | i2c_del_driver(&dm355evm_msp_driver); | ||
416 | } | ||
417 | module_exit(dm355evm_msp_exit); | ||
418 | |||
419 | MODULE_DESCRIPTION("Interface to MSP430 firmware on DM355EVM"); | ||
420 | MODULE_LICENSE("GPL"); | ||