/* * Driver an MMC/SD card on a bitbanging GPIO SPI bus. * This module hooks up the mmc_spi and spi_gpio modules and also * provides a configfs interface. * * Copyright 2008 Michael Buesch * * Licensed under the GNU/GPL. See COPYING for details. */ #include #include #include #include #include #include #include #include #include #define PFX "gpio-mmc: " struct gpiommc_device { struct platform_device *pdev; struct platform_device *spi_pdev; struct spi_board_info boardinfo; }; MODULE_DESCRIPTION("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; struct gpiommc_platform_data *pdata = d->pdev->dev.platform_data; /* Bind the SPI master to the MMC-SPI host driver. */ strlcpy(bi->modalias, "mmc_spi", sizeof(bi->modalias)); bi->max_speed_hz = pdata->max_bus_speed; bi->bus_num = master->bus_num; bi->mode = pdata->mode; return 0; } static int gpiommc_probe(struct platform_device *pdev) { struct gpiommc_platform_data *mmc_pdata = pdev->dev.platform_data; struct spi_gpio_platform_data spi_pdata; struct gpiommc_device *d; int err; err = -ENXIO; if (!mmc_pdata) goto error; #ifdef CONFIG_MMC_SPI_MODULE err = request_module("mmc_spi"); if (err) { printk(KERN_WARNING PFX "Failed to request mmc_spi module.\n"); } #endif /* CONFIG_MMC_SPI_MODULE */ /* Allocate the GPIO-MMC device */ err = -ENOMEM; d = kzalloc(sizeof(*d), GFP_KERNEL); if (!d) goto error; d->pdev = pdev; /* Create the SPI-GPIO device */ d->spi_pdev = platform_device_alloc(SPI_GPIO_PLATDEV_NAME, spi_gpio_next_id()); if (!d->spi_pdev) goto err_free_d; memset(&spi_pdata, 0, sizeof(spi_pdata)); spi_pdata.pin_clk = mmc_pdata->pins.gpio_clk; spi_pdata.pin_miso = mmc_pdata->pins.gpio_do; spi_pdata.pin_mosi = mmc_pdata->pins.gpio_di; spi_pdata.pin_cs = mmc_pdata->pins.gpio_cs; spi_pdata.cs_activelow = mmc_pdata->pins.cs_activelow; spi_pdata.no_spi_delay = mmc_pdata->no_spi_delay; spi_pdata.boardinfo_setup = gpiommc_boardinfo_setup; spi_pdata.boardinfo_setup_data = d; err = platform_device_add_data(d->spi_pdev, &spi_pdata, sizeof(spi_pdata)); if (err) goto err_free_pdev; err = platform_device_add(d->spi_pdev); if (err) goto err_free_pdata; platform_set_drvdata(pdev, d); printk(KERN_INFO PFX "MMC-Card \"%s\" " "attached to GPIO pins di=%u, do=%u, clk=%u, cs=%u\n", mmc_pdata->name, mmc_pdata->pins.gpio_di, mmc_pdata->pins.gpio_do, mmc_pdata->pins.gpio_clk, mmc_pdata->pins.gpio_cs); return 0; 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); err_free_d: kfree(d); error: return err; } static int gpiommc_remove(struct platform_device *pdev) { struct gpiommc_device *d = platform_get_drvdata(pdev); struct gpiommc_platform_data *pdata = d->pdev->dev.platform_data; platform_device_unregister(d->spi_pdev); printk(KERN_INFO PFX "GPIO based MMC-Card \"%s\" removed\n", pdata->name); platform_device_put(d->spi_pdev); return 0; } #ifdef CONFIG_GPIOMMC_CONFIGFS /* A device that was created through configfs */ struct gpiommc_configfs_device { struct config_item item; /* The platform device, after registration. */ struct platform_device *pdev; /* The configuration */ struct gpiommc_platform_data pdata; /* Mutex to protect this structure */ struct mutex mutex; }; #define GPIO_INVALID -1 static inline bool gpiommc_is_registered(struct gpiommc_configfs_device *dev) { return (dev->pdev != NULL); } static inline struct gpiommc_configfs_device *ci_to_gpiommc(struct config_item *item) { return item ? container_of(item, struct gpiommc_configfs_device, item) : NULL; } static struct configfs_attribute gpiommc_attr_DI = { .ca_owner = THIS_MODULE, .ca_name = "gpio_data_in", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_DO = { .ca_owner = THIS_MODULE, .ca_name = "gpio_data_out", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_CLK = { .ca_owner = THIS_MODULE, .ca_name = "gpio_clock", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_CS = { .ca_owner = THIS_MODULE, .ca_name = "gpio_chipselect", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_CS_activelow = { .ca_owner = THIS_MODULE, .ca_name = "gpio_chipselect_activelow", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_spimode = { .ca_owner = THIS_MODULE, .ca_name = "spi_mode", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_spidelay = { .ca_owner = THIS_MODULE, .ca_name = "spi_delay", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_max_bus_speed = { .ca_owner = THIS_MODULE, .ca_name = "max_bus_speed", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute gpiommc_attr_register = { .ca_owner = THIS_MODULE, .ca_name = "register", .ca_mode = S_IRUGO | S_IWUSR, }; static struct configfs_attribute *gpiommc_config_attrs[] = { &gpiommc_attr_DI, &gpiommc_attr_DO, &gpiommc_attr_CLK, &gpiommc_attr_CS, &gpiommc_attr_CS_activelow, &gpiommc_attr_spimode, &gpiommc_attr_spidelay, &gpiommc_attr_max_bus_speed, &gpiommc_attr_register, NULL, }; static ssize_t gpiommc_config_attr_show(struct config_item *item, struct configfs_attribute *attr, char *page) { struct gpiommc_configfs_device *dev = ci_to_gpiommc(item); ssize_t count = 0; unsigned int gpio; int err = 0; mutex_lock(&dev->mutex); if (attr == &gpiommc_attr_DI) { gpio = dev->pdata.pins.gpio_di; if (gpio == GPIO_INVALID) count = snprintf(page, PAGE_SIZE, "not configured\n"); else count = snprintf(page, PAGE_SIZE, "%u\n", gpio); goto out; } if (attr == &gpiommc_attr_DO) { gpio = dev->pdata.pins.gpio_do; if (gpio == GPIO_INVALID) count = snprintf(page, PAGE_SIZE, "not configured\n"); else count = snprintf(page, PAGE_SIZE, "%u\n", gpio); goto out; } if (attr == &gpiommc_attr_CLK) { gpio = dev->pdata.pins.gpio_clk; if (gpio == GPIO_INVALID) count = snprintf(page, PAGE_SIZE, "not configured\n"); else count = snprintf(page, PAGE_SIZE, "%u\n", gpio); goto out; } if (attr == &gpiommc_attr_CS) { gpio = dev->pdata.pins.gpio_cs; if (gpio == GPIO_INVALID) count = snprintf(page, PAGE_SIZE, "not configured\n"); else count = snprintf(page, PAGE_SIZE, "%u\n", gpio); goto out; } if (attr == &gpiommc_attr_CS_activelow) { count = snprintf(page, PAGE_SIZE, "%u\n", dev->pdata.pins.cs_activelow); goto out; } if (attr == &gpiommc_attr_spimode) { count = snprintf(page, PAGE_SIZE, "%u\n", dev->pdata.mode); goto out; } if (attr == &gpiommc_attr_spidelay) { count = snprintf(page, PAGE_SIZE, "%u\n", !dev->pdata.no_spi_delay); goto out; } if (attr == &gpiommc_attr_max_bus_speed) { count = snprintf(page, PAGE_SIZE, "%u\n", dev->pdata.max_bus_speed); goto out; } if (attr == &gpiommc_attr_register) { count = snprintf(page, PAGE_SIZE, "%u\n", gpiommc_is_registered(dev)); goto out; } WARN_ON(1); err = -ENOSYS; out: mutex_unlock(&dev->mutex); return err ? err : count; } static int gpiommc_do_register(struct gpiommc_configfs_device *dev, const char *name) { int err; if (gpiommc_is_registered(dev)) return 0; if (!gpio_is_valid(dev->pdata.pins.gpio_di) || !gpio_is_valid(dev->pdata.pins.gpio_do) || !gpio_is_valid(dev->pdata.pins.gpio_clk) || !gpio_is_valid(dev->pdata.pins.gpio_cs)) { printk(KERN_ERR PFX "configfs: Invalid GPIO pin number(s)\n"); return -EINVAL; } strlcpy(dev->pdata.name, name, sizeof(dev->pdata.name)); dev->pdev = platform_device_alloc(GPIOMMC_PLATDEV_NAME, gpiommc_next_id()); if (!dev->pdev) return -ENOMEM; err = platform_device_add_data(dev->pdev, &dev->pdata, sizeof(dev->pdata)); if (err) { platform_device_put(dev->pdev); return err; } err = platform_device_add(dev->pdev); if (err) { platform_device_put(dev->pdev); return err; } return 0; } static void gpiommc_do_unregister(struct gpiommc_configfs_device *dev) { if (!gpiommc_is_registered(dev)) return; platform_device_unregister(dev->pdev); dev->pdev = NULL; } static ssize_t gpiommc_config_attr_store(struct config_item *item, struct configfs_attribute *attr, const char *page, size_t count) { struct gpiommc_configfs_device *dev = ci_to_gpiommc(item); int err = -EINVAL; unsigned long data; mutex_lock(&dev->mutex); if (attr == &gpiommc_attr_register) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (data == 1) err = gpiommc_do_register(dev, item->ci_name); if (data == 0) { gpiommc_do_unregister(dev); err = 0; } goto out; } if (gpiommc_is_registered(dev)) { /* The rest of the config parameters can only be set * as long as the device is not registered, yet. */ err = -EBUSY; goto out; } if (attr == &gpiommc_attr_DI) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (!gpio_is_valid(data)) goto out; dev->pdata.pins.gpio_di = data; err = 0; goto out; } if (attr == &gpiommc_attr_DO) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (!gpio_is_valid(data)) goto out; dev->pdata.pins.gpio_do = data; err = 0; goto out; } if (attr == &gpiommc_attr_CLK) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (!gpio_is_valid(data)) goto out; dev->pdata.pins.gpio_clk = data; err = 0; goto out; } if (attr == &gpiommc_attr_CS) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (!gpio_is_valid(data)) goto out; dev->pdata.pins.gpio_cs = data; err = 0; goto out; } if (attr == &gpiommc_attr_CS_activelow) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (data != 0 && data != 1) goto out; dev->pdata.pins.cs_activelow = data; err = 0; goto out; } if (attr == &gpiommc_attr_spimode) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; switch (data) { case 0: dev->pdata.mode = SPI_MODE_0; break; case 1: dev->pdata.mode = SPI_MODE_1; break; case 2: dev->pdata.mode = SPI_MODE_2; break; case 3: dev->pdata.mode = SPI_MODE_3; break; default: goto out; } err = 0; goto out; } if (attr == &gpiommc_attr_spidelay) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (data != 0 && data != 1) goto out; dev->pdata.no_spi_delay = !data; err = 0; goto out; } if (attr == &gpiommc_attr_max_bus_speed) { err = kstrtoul(page, 10, &data); if (err) goto out; err = -EINVAL; if (data > UINT_MAX) goto out; dev->pdata.max_bus_speed = data; err = 0; goto out; } WARN_ON(1); err = -ENOSYS; out: mutex_unlock(&dev->mutex); return err ? err : count; } static void gpiommc_config_item_release(struct config_item *item) { struct gpiommc_configfs_device *dev = ci_to_gpiommc(item); kfree(dev); } static struct configfs_item_operations gpiommc_config_item_ops = { .release = gpiommc_config_item_release, .show_attribute = gpiommc_config_attr_show, .store_attribute = gpiommc_config_attr_store, }; static struct config_item_type gpiommc_dev_ci_type = { .ct_item_ops = &gpiommc_config_item_ops, .ct_attrs = gpiommc_config_attrs, .ct_owner = THIS_MODULE, }; static struct config_item *gpiommc_make_item(struct config_group *group, const char *name) { struct gpiommc_configfs_device *dev; if (strlen(name) > GPIOMMC_MAX_NAMELEN) { printk(KERN_ERR PFX "configfs: device name too long\n"); return NULL; } dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return NULL; mutex_init(&dev->mutex); config_item_init_type_name(&dev->item, name, &gpiommc_dev_ci_type); /* Assign default configuration */ dev->pdata.pins.gpio_di = GPIO_INVALID; dev->pdata.pins.gpio_do = GPIO_INVALID; dev->pdata.pins.gpio_clk = GPIO_INVALID; dev->pdata.pins.gpio_cs = GPIO_INVALID; dev->pdata.pins.cs_activelow = 1; dev->pdata.mode = SPI_MODE_0; dev->pdata.no_spi_delay = 0; dev->pdata.max_bus_speed = 5000000; /* 5 MHz */ return &(dev->item); } static void gpiommc_drop_item(struct config_group *group, struct config_item *item) { struct gpiommc_configfs_device *dev = ci_to_gpiommc(item); gpiommc_do_unregister(dev); kfree(dev); } static struct configfs_group_operations gpiommc_ct_group_ops = { .make_item = gpiommc_make_item, .drop_item = gpiommc_drop_item, }; static struct config_item_type gpiommc_ci_type = { .ct_group_ops = &gpiommc_ct_group_ops, .ct_owner = THIS_MODULE, }; static struct configfs_subsystem gpiommc_subsys = { .su_group = { .cg_item = { .ci_namebuf = GPIOMMC_PLATDEV_NAME, .ci_type = &gpiommc_ci_type, }, }, .su_mutex = __MUTEX_INITIALIZER(gpiommc_subsys.su_mutex), }; #endif /* CONFIG_GPIOMMC_CONFIGFS */ static struct platform_driver gpiommc_plat_driver = { .probe = gpiommc_probe, .remove = gpiommc_remove, .driver = { .name = GPIOMMC_PLATDEV_NAME, .owner = THIS_MODULE, }, }; int gpiommc_next_id(void) { static atomic_t counter = ATOMIC_INIT(-1); return atomic_inc_return(&counter); } EXPORT_SYMBOL(gpiommc_next_id); static int __init gpiommc_modinit(void) { int err; err = platform_driver_register(&gpiommc_plat_driver); if (err) return err; #ifdef CONFIG_GPIOMMC_CONFIGFS config_group_init(&gpiommc_subsys.su_group); err = configfs_register_subsystem(&gpiommc_subsys); if (err) { platform_driver_unregister(&gpiommc_plat_driver); return err; } #endif /* CONFIG_GPIOMMC_CONFIGFS */ return 0; } module_init(gpiommc_modinit); static void __exit gpiommc_modexit(void) { #ifdef CONFIG_GPIOMMC_CONFIGFS configfs_unregister_subsystem(&gpiommc_subsys); #endif platform_driver_unregister(&gpiommc_plat_driver); } module_exit(gpiommc_modexit);