| /* |
| * 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 <mb@bu3sch.de> |
| * |
| * Licensed under the GNU/GPL. See COPYING for details. |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/mmc/gpiommc.h> |
| #include <linux/platform_device.h> |
| #include <linux/list.h> |
| #include <linux/mutex.h> |
| #include <linux/spi/spi_gpio_old.h> |
| #include <linux/configfs.h> |
| #include <linux/gpio.h> |
| #include <asm/atomic.h> |
| |
| |
| #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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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 = strict_strtoul(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); |