/drivers/mfd/dm355evm_msp.c
C | 437 lines | 288 code | 71 blank | 78 comment | 32 complexity | 3b5ae66e74e745d1b08cf471e9f3c3d3 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, AGPL-1.0
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_INPUT_DM355EVM) || defined(CONFIG_INPUT_DM355EVM_MODULE) 36#define msp_has_keyboard() true 37#else 38#define msp_has_keyboard() false 39#endif 40 41#if defined(CONFIG_LEDS_GPIO) || defined(CONFIG_LEDS_GPIO_MODULE) 42#define msp_has_leds() true 43#else 44#define msp_has_leds() false 45#endif 46 47#if defined(CONFIG_RTC_DRV_DM355EVM) || defined(CONFIG_RTC_DRV_DM355EVM_MODULE) 48#define msp_has_rtc() true 49#else 50#define msp_has_rtc() false 51#endif 52 53#if defined(CONFIG_VIDEO_TVP514X) || defined(CONFIG_VIDEO_TVP514X_MODULE) 54#define msp_has_tvp() true 55#else 56#define msp_has_tvp() false 57#endif 58 59 60/*----------------------------------------------------------------------*/ 61 62/* REVISIT for paranoia's sake, retry reads/writes on error */ 63 64static struct i2c_client *msp430; 65 66/** 67 * dm355evm_msp_write - Writes a register in dm355evm_msp 68 * @value: the value to be written 69 * @reg: register address 70 * 71 * Returns result of operation - 0 is success, else negative errno 72 */ 73int dm355evm_msp_write(u8 value, u8 reg) 74{ 75 return i2c_smbus_write_byte_data(msp430, reg, value); 76} 77EXPORT_SYMBOL(dm355evm_msp_write); 78 79/** 80 * dm355evm_msp_read - Reads a register from dm355evm_msp 81 * @reg: register address 82 * 83 * Returns result of operation - value, or negative errno 84 */ 85int dm355evm_msp_read(u8 reg) 86{ 87 return i2c_smbus_read_byte_data(msp430, reg); 88} 89EXPORT_SYMBOL(dm355evm_msp_read); 90 91/*----------------------------------------------------------------------*/ 92 93/* 94 * Many of the msp430 pins are just used as fixed-direction GPIOs. 95 * We could export a few more of them this way, if we wanted. 96 */ 97#define MSP_GPIO(bit,reg) ((DM355EVM_MSP_ ## reg) << 3 | (bit)) 98 99static const u8 msp_gpios[] = { 100 /* eight leds */ 101 MSP_GPIO(0, LED), MSP_GPIO(1, LED), 102 MSP_GPIO(2, LED), MSP_GPIO(3, LED), 103 MSP_GPIO(4, LED), MSP_GPIO(5, LED), 104 MSP_GPIO(6, LED), MSP_GPIO(7, LED), 105 /* SW6 and the NTSC/nPAL jumper */ 106 MSP_GPIO(0, SWITCH1), MSP_GPIO(1, SWITCH1), 107 MSP_GPIO(2, SWITCH1), MSP_GPIO(3, SWITCH1), 108 MSP_GPIO(4, SWITCH1), 109 /* switches on MMC/SD sockets */ 110 /* 111 * Note: EVMDM355_ECP_VA4.pdf suggests that Bit 2 and 4 should be 112 * checked for card detection. However on the EVM bit 1 and 3 gives 113 * this status, for 0 and 1 instance respectively. The pdf also 114 * suggests that Bit 1 and 3 should be checked for write protection. 115 * However on the EVM bit 2 and 4 gives this status,for 0 and 1 116 * instance respectively. 117 */ 118 MSP_GPIO(2, SDMMC), MSP_GPIO(1, SDMMC), /* mmc0 WP, nCD */ 119 MSP_GPIO(4, SDMMC), MSP_GPIO(3, SDMMC), /* mmc1 WP, nCD */ 120}; 121 122#define MSP_GPIO_REG(offset) (msp_gpios[(offset)] >> 3) 123#define MSP_GPIO_MASK(offset) BIT(msp_gpios[(offset)] & 0x07) 124 125static int msp_gpio_in(struct gpio_chip *chip, unsigned offset) 126{ 127 switch (MSP_GPIO_REG(offset)) { 128 case DM355EVM_MSP_SWITCH1: 129 case DM355EVM_MSP_SWITCH2: 130 case DM355EVM_MSP_SDMMC: 131 return 0; 132 default: 133 return -EINVAL; 134 } 135} 136 137static u8 msp_led_cache; 138 139static int msp_gpio_get(struct gpio_chip *chip, unsigned offset) 140{ 141 int reg, status; 142 143 reg = MSP_GPIO_REG(offset); 144 status = dm355evm_msp_read(reg); 145 if (status < 0) 146 return status; 147 if (reg == DM355EVM_MSP_LED) 148 msp_led_cache = status; 149 return status & MSP_GPIO_MASK(offset); 150} 151 152static int msp_gpio_out(struct gpio_chip *chip, unsigned offset, int value) 153{ 154 int mask, bits; 155 156 /* NOTE: there are some other signals that could be 157 * packaged as output GPIOs, but they aren't as useful 158 * as the LEDs ... so for now we don't. 159 */ 160 if (MSP_GPIO_REG(offset) != DM355EVM_MSP_LED) 161 return -EINVAL; 162 163 mask = MSP_GPIO_MASK(offset); 164 bits = msp_led_cache; 165 166 bits &= ~mask; 167 if (value) 168 bits |= mask; 169 msp_led_cache = bits; 170 171 return dm355evm_msp_write(bits, DM355EVM_MSP_LED); 172} 173 174static void msp_gpio_set(struct gpio_chip *chip, unsigned offset, int value) 175{ 176 msp_gpio_out(chip, offset, value); 177} 178 179static struct gpio_chip dm355evm_msp_gpio = { 180 .label = "dm355evm_msp", 181 .owner = THIS_MODULE, 182 .direction_input = msp_gpio_in, 183 .get = msp_gpio_get, 184 .direction_output = msp_gpio_out, 185 .set = msp_gpio_set, 186 .base = -EINVAL, /* dynamic assignment */ 187 .ngpio = ARRAY_SIZE(msp_gpios), 188 .can_sleep = true, 189}; 190 191/*----------------------------------------------------------------------*/ 192 193static struct device *add_child(struct i2c_client *client, const char *name, 194 void *pdata, unsigned pdata_len, 195 bool can_wakeup, int irq) 196{ 197 struct platform_device *pdev; 198 int status; 199 200 pdev = platform_device_alloc(name, -1); 201 if (!pdev) { 202 dev_dbg(&client->dev, "can't alloc dev\n"); 203 status = -ENOMEM; 204 goto err; 205 } 206 207 device_init_wakeup(&pdev->dev, can_wakeup); 208 pdev->dev.parent = &client->dev; 209 210 if (pdata) { 211 status = platform_device_add_data(pdev, pdata, pdata_len); 212 if (status < 0) { 213 dev_dbg(&pdev->dev, "can't add platform_data\n"); 214 goto err; 215 } 216 } 217 218 if (irq) { 219 struct resource r = { 220 .start = irq, 221 .flags = IORESOURCE_IRQ, 222 }; 223 224 status = platform_device_add_resources(pdev, &r, 1); 225 if (status < 0) { 226 dev_dbg(&pdev->dev, "can't add irq\n"); 227 goto err; 228 } 229 } 230 231 status = platform_device_add(pdev); 232 233err: 234 if (status < 0) { 235 platform_device_put(pdev); 236 dev_err(&client->dev, "can't add %s dev\n", name); 237 return ERR_PTR(status); 238 } 239 return &pdev->dev; 240} 241 242static int add_children(struct i2c_client *client) 243{ 244 static const struct { 245 int offset; 246 char *label; 247 } config_inputs[] = { 248 /* 8 == right after the LEDs */ 249 { 8 + 0, "sw6_1", }, 250 { 8 + 1, "sw6_2", }, 251 { 8 + 2, "sw6_3", }, 252 { 8 + 3, "sw6_4", }, 253 { 8 + 4, "NTSC/nPAL", }, 254 }; 255 256 struct device *child; 257 int status; 258 int i; 259 260 /* GPIO-ish stuff */ 261 dm355evm_msp_gpio.dev = &client->dev; 262 status = gpiochip_add(&dm355evm_msp_gpio); 263 if (status < 0) 264 return status; 265 266 /* LED output */ 267 if (msp_has_leds()) { 268#define GPIO_LED(l) .name = l, .active_low = true 269 static struct gpio_led evm_leds[] = { 270 { GPIO_LED("dm355evm::ds14"), 271 .default_trigger = "heartbeat", }, 272 { GPIO_LED("dm355evm::ds15"), 273 .default_trigger = "mmc0", }, 274 { GPIO_LED("dm355evm::ds16"), 275 /* could also be a CE-ATA drive */ 276 .default_trigger = "mmc1", }, 277 { GPIO_LED("dm355evm::ds17"), 278 .default_trigger = "nand-disk", }, 279 { GPIO_LED("dm355evm::ds18"), }, 280 { GPIO_LED("dm355evm::ds19"), }, 281 { GPIO_LED("dm355evm::ds20"), }, 282 { GPIO_LED("dm355evm::ds21"), }, 283 }; 284#undef GPIO_LED 285 286 struct gpio_led_platform_data evm_led_data = { 287 .num_leds = ARRAY_SIZE(evm_leds), 288 .leds = evm_leds, 289 }; 290 291 for (i = 0; i < ARRAY_SIZE(evm_leds); i++) 292 evm_leds[i].gpio = i + dm355evm_msp_gpio.base; 293 294 /* NOTE: these are the only fully programmable LEDs 295 * on the board, since GPIO-61/ds22 (and many signals 296 * going to DC7) must be used for AEMIF address lines 297 * unless the top 1 GB of NAND is unused... 298 */ 299 child = add_child(client, "leds-gpio", 300 &evm_led_data, sizeof(evm_led_data), 301 false, 0); 302 if (IS_ERR(child)) 303 return PTR_ERR(child); 304 } 305 306 /* configuration inputs */ 307 for (i = 0; i < ARRAY_SIZE(config_inputs); i++) { 308 int gpio = dm355evm_msp_gpio.base + config_inputs[i].offset; 309 310 gpio_request(gpio, config_inputs[i].label); 311 gpio_direction_input(gpio); 312 313 /* make it easy for userspace to see these */ 314 gpio_export(gpio, false); 315 } 316 317 /* MMC/SD inputs -- right after the last config input */ 318 if (client->dev.platform_data) { 319 void (*mmcsd_setup)(unsigned) = client->dev.platform_data; 320 321 mmcsd_setup(dm355evm_msp_gpio.base + 8 + 5); 322 } 323 324 /* RTC is a 32 bit counter, no alarm */ 325 if (msp_has_rtc()) { 326 child = add_child(client, "rtc-dm355evm", 327 NULL, 0, false, 0); 328 if (IS_ERR(child)) 329 return PTR_ERR(child); 330 } 331 332 /* input from buttons and IR remote (uses the IRQ) */ 333 if (msp_has_keyboard()) { 334 child = add_child(client, "dm355evm_keys", 335 NULL, 0, true, client->irq); 336 if (IS_ERR(child)) 337 return PTR_ERR(child); 338 } 339 340 return 0; 341} 342 343/*----------------------------------------------------------------------*/ 344 345static void dm355evm_command(unsigned command) 346{ 347 int status; 348 349 status = dm355evm_msp_write(command, DM355EVM_MSP_COMMAND); 350 if (status < 0) 351 dev_err(&msp430->dev, "command %d failure %d\n", 352 command, status); 353} 354 355static void dm355evm_power_off(void) 356{ 357 dm355evm_command(MSP_COMMAND_POWEROFF); 358} 359 360static int dm355evm_msp_remove(struct i2c_client *client) 361{ 362 pm_power_off = NULL; 363 msp430 = NULL; 364 return 0; 365} 366 367static int 368dm355evm_msp_probe(struct i2c_client *client, const struct i2c_device_id *id) 369{ 370 int status; 371 const char *video = msp_has_tvp() ? "TVP5146" : "imager"; 372 373 if (msp430) 374 return -EBUSY; 375 msp430 = client; 376 377 /* display revision status; doubles as sanity check */ 378 status = dm355evm_msp_read(DM355EVM_MSP_FIRMREV); 379 if (status < 0) 380 goto fail; 381 dev_info(&client->dev, "firmware v.%02X, %s as video-in\n", 382 status, video); 383 384 /* mux video input: either tvp5146 or some external imager */ 385 status = dm355evm_msp_write(msp_has_tvp() ? 0 : MSP_VIDEO_IMAGER, 386 DM355EVM_MSP_VIDEO_IN); 387 if (status < 0) 388 dev_warn(&client->dev, "error %d muxing %s as video-in\n", 389 status, video); 390 391 /* init LED cache, and turn off the LEDs */ 392 msp_led_cache = 0xff; 393 dm355evm_msp_write(msp_led_cache, DM355EVM_MSP_LED); 394 395 /* export capabilities we support */ 396 status = add_children(client); 397 if (status < 0) 398 goto fail; 399 400 /* PM hookup */ 401 pm_power_off = dm355evm_power_off; 402 403 return 0; 404 405fail: 406 /* FIXME remove children ... */ 407 dm355evm_msp_remove(client); 408 return status; 409} 410 411static const struct i2c_device_id dm355evm_msp_ids[] = { 412 { "dm355evm_msp", 0 }, 413 { /* end of list */ }, 414}; 415MODULE_DEVICE_TABLE(i2c, dm355evm_msp_ids); 416 417static struct i2c_driver dm355evm_msp_driver = { 418 .driver.name = "dm355evm_msp", 419 .id_table = dm355evm_msp_ids, 420 .probe = dm355evm_msp_probe, 421 .remove = dm355evm_msp_remove, 422}; 423 424static int __init dm355evm_msp_init(void) 425{ 426 return i2c_add_driver(&dm355evm_msp_driver); 427} 428subsys_initcall(dm355evm_msp_init); 429 430static void __exit dm355evm_msp_exit(void) 431{ 432 i2c_del_driver(&dm355evm_msp_driver); 433} 434module_exit(dm355evm_msp_exit); 435 436MODULE_DESCRIPTION("Interface to MSP430 firmware on DM355EVM"); 437MODULE_LICENSE("GPL");