/* * Driver for driving an MMC card over a bitbanging GPIO SPI bus. * * Copyright 2008 Michael Buesch * * Licensed under the GNU/GPL. See COPYING for details. */ #include #include #include #include "linux/spi/spi_gpio.h" //XXX /* This is the maximum speed in Hz */ #define GPIOMMC_MAXSPEED 5000000 /* Hz */ #define DRIVER_NAME "spi-gpio-mmc" #define PFX DRIVER_NAME ": " #define GPIOMMC_MAX_NAMELEN 15 #define GPIOMMC_MAX_NAMELEN_STR __stringify(GPIOMMC_MAX_NAMELEN) struct gpiommc_pins { unsigned int gpio_di; /* Card DI pin */ unsigned int gpio_do; /* Card DO pin */ unsigned int gpio_clk; /* Card CLK pin */ unsigned int gpio_cs; /* Card CS pin */ }; struct gpiommc_device { char name[GPIOMMC_MAX_NAMELEN + 1]; struct platform_device *pdev; struct platform_device *spi_pdev; struct gpiommc_pins pins; u8 mode; /* SPI_MODE_X */ struct spi_board_info boardinfo; struct list_head list; }; static LIST_HEAD(gpiommc_devices_list); static DEFINE_MUTEX(gpiommc_mutex); MODULE_DESCRIPTION("SPI-GPIO based MMC driver"); MODULE_AUTHOR("Michael Buesch"); MODULE_LICENSE("GPL"); static int gpiommc_boardinfo_setup(struct spi_board_info *bi, struct spi_master *master, void *data) { struct gpiommc_device *d = data; /* Bind the SPI master to the MMC-SPI host driver. */ strlcpy(bi->modalias, "mmc_spi", sizeof(bi->modalias)); bi->max_speed_hz = GPIOMMC_MAXSPEED; bi->bus_num = master->bus_num; bi->mode = d->mode; return 0; } static int gpiommc_probe(struct platform_device *pdev) { static int instance; struct gpiommc_device *d = platform_get_drvdata(pdev); struct spi_gpio_platform_data pdata; int err = -ENOMEM; d->spi_pdev = platform_device_alloc("spi-gpio", instance++); if (!d->spi_pdev) goto out; memset(&pdata, 0, sizeof(pdata)); pdata.pin_clk = d->pins.gpio_clk; pdata.pin_miso = d->pins.gpio_do; pdata.pin_mosi = d->pins.gpio_di; pdata.pin_cs = d->pins.gpio_cs; pdata.cs_activelow = 1; pdata.no_spi_delay = 1; pdata.boardinfo_setup = gpiommc_boardinfo_setup; pdata.boardinfo_setup_data = d; err = platform_device_add_data(d->spi_pdev, &pdata, sizeof(pdata)); if (err) goto err_free_pdev; err = platform_device_register(d->spi_pdev); if (err) goto err_free_pdata; printk(KERN_INFO PFX "MMC-Card \"%s\" " "attached to GPIO pins %u,%u,%u,%u\n", d->name, d->pins.gpio_di, d->pins.gpio_do, d->pins.gpio_clk, d->pins.gpio_cs); out: return err; err_free_pdata: kfree(d->spi_pdev->dev.platform_data); d->spi_pdev->dev.platform_data = NULL; err_free_pdev: platform_device_put(d->spi_pdev); return err; } static int gpiommc_remove(struct platform_device *pdev) { struct gpiommc_device *d = platform_get_drvdata(pdev); platform_device_unregister(d->spi_pdev); printk(KERN_INFO PFX "MMC-Card \"%s\" removed\n", d->name); return 0; } static void gpiommc_free(struct gpiommc_device *d) { kfree(d); } static struct gpiommc_device * gpiommc_alloc(struct platform_device *pdev, const char *name, const struct gpiommc_pins *pins, u8 mode) { struct gpiommc_device *d; d = kmalloc(sizeof(*d), GFP_KERNEL); if (!d) return NULL; strcpy(d->name, name); memcpy(&d->pins, pins, sizeof(d->pins)); d->mode = mode; INIT_LIST_HEAD(&d->list); return d; } /* List must be locked. */ static struct gpiommc_device * gpiommc_find_device(const char *name) { struct gpiommc_device *d; list_for_each_entry(d, &gpiommc_devices_list, list) { if (strcmp(d->name, name) == 0) return d; } return NULL; } static void gpiommc_do_destroy_device(struct gpiommc_device *d) { list_del(&d->list); platform_device_unregister(d->pdev); gpiommc_free(d); } static int gpiommc_destroy_device(const char *name) { struct gpiommc_device *d; int err = -ENODEV; mutex_lock(&gpiommc_mutex); d = gpiommc_find_device(name); if (!d) goto out_unlock; gpiommc_do_destroy_device(d); err = 0; out_unlock: mutex_unlock(&gpiommc_mutex); return err; } static int gpiommc_create_device(const char *name, const struct gpiommc_pins *pins, u8 mode) { static int instance; struct platform_device *pdev; struct gpiommc_device *d; int err; mutex_lock(&gpiommc_mutex); err = -EEXIST; if (gpiommc_find_device(name)) goto out_unlock; err = -ENOMEM; pdev = platform_device_alloc(DRIVER_NAME, instance++); if (!pdev) goto out_unlock; d = gpiommc_alloc(pdev, name, pins, mode); if (!d) goto err_free_pdev; platform_set_drvdata(pdev, d); d->pdev = pdev; err = platform_device_register(pdev); if (err) goto err_free_mdev; list_add(&d->list, &gpiommc_devices_list); err = 0; out_unlock: mutex_unlock(&gpiommc_mutex); return err; err_free_mdev: gpiommc_free(d); err_free_pdev: platform_device_put(pdev); goto out_unlock; } static ssize_t gpiommc_add_show(struct device_driver *drv, char *buf) { return snprintf(buf, PAGE_SIZE, "NAME DI_pin,DO_pin,CLK_pin,CS_pin [MODE]\n"); } static ssize_t gpiommc_add_store(struct device_driver *drv, const char *buf, size_t count) { int res, err; char name[GPIOMMC_MAX_NAMELEN + 1]; struct gpiommc_pins pins; unsigned int mode; res = sscanf(buf, "%" GPIOMMC_MAX_NAMELEN_STR "s %u,%u,%u,%u %u", name, &pins.gpio_di, &pins.gpio_do, &pins.gpio_clk, &pins.gpio_cs, &mode); if (res == 5) mode = 0; else if (res != 6) return -EINVAL; switch (mode) { case 0: mode = SPI_MODE_0; break; case 1: mode = SPI_MODE_1; break; case 2: mode = SPI_MODE_2; break; case 3: mode = SPI_MODE_3; break; default: return -EINVAL; } err = gpiommc_create_device(name, &pins, mode); return err ? err : count; } static ssize_t gpiommc_remove_show(struct device_driver *drv, char *buf) { return snprintf(buf, PAGE_SIZE, "write device-name to remove the device\n"); } static ssize_t gpiommc_remove_store(struct device_driver *drv, const char *buf, size_t count) { int err; err = gpiommc_destroy_device(buf); return err ? err : count; } static DRIVER_ATTR(add, 0600, gpiommc_add_show, gpiommc_add_store); static DRIVER_ATTR(remove, 0600, gpiommc_remove_show, gpiommc_remove_store); static struct platform_driver gpiommc_plat_driver = { .probe = gpiommc_probe, .remove = gpiommc_remove, .driver = { .name = DRIVER_NAME, .owner = THIS_MODULE, }, }; static int __init gpiommc_modinit(void) { int err; err = platform_driver_register(&gpiommc_plat_driver); if (err) return err; err = driver_create_file(&gpiommc_plat_driver.driver, &driver_attr_add); if (err) goto err_drv_unreg; err = driver_create_file(&gpiommc_plat_driver.driver, &driver_attr_remove); if (err) goto err_remove_add; return 0; err_remove_add: driver_remove_file(&gpiommc_plat_driver.driver, &driver_attr_add); err_drv_unreg: platform_driver_unregister(&gpiommc_plat_driver); return err; } module_init(gpiommc_modinit); static void __exit gpiommc_modexit(void) { struct gpiommc_device *d, *tmp; driver_remove_file(&gpiommc_plat_driver.driver, &driver_attr_remove); driver_remove_file(&gpiommc_plat_driver.driver, &driver_attr_add); mutex_lock(&gpiommc_mutex); list_for_each_entry_safe(d, tmp, &gpiommc_devices_list, list) gpiommc_do_destroy_device(d); mutex_unlock(&gpiommc_mutex); platform_driver_unregister(&gpiommc_plat_driver); } module_exit(gpiommc_modexit);