/* * GPIO driver for Analog Devices ADP5520 MFD PMICs * * Copyright 2009 Analog Devices Inc. * * Licensed under the GPL-2 or later. */ #include <linux/module.h> #include <linux/slab.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/platform_device.h> #include <linux/mfd/adp5520.h> #include <linux/gpio.h> struct adp5520_gpio { struct device *master; struct gpio_chip gpio_chip; unsigned char lut[ADP5520_MAXGPIOS]; unsigned long output; }; static int adp5520_gpio_get_value(struct gpio_chip *chip, unsigned off) { struct adp5520_gpio *dev; uint8_t reg_val; dev = container_of(chip, struct adp5520_gpio, gpio_chip); /* * There are dedicated registers for GPIO IN/OUT. * Make sure we return the right value, even when configured as output */ if (test_bit(off, &dev->output)) adp5520_read(dev->master, ADP5520_GPIO_OUT, ®_val); else adp5520_read(dev->master, ADP5520_GPIO_IN, ®_val); return !!(reg_val & dev->lut[off]); } static void adp5520_gpio_set_value(struct gpio_chip *chip, unsigned off, int val) { struct adp5520_gpio *dev; dev = container_of(chip, struct adp5520_gpio, gpio_chip); if (val) adp5520_set_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]); else adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]); } static int adp5520_gpio_direction_input(struct gpio_chip *chip, unsigned off) { struct adp5520_gpio *dev; dev = container_of(chip, struct adp5520_gpio, gpio_chip); clear_bit(off, &dev->output); return adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_2, dev->lut[off]); } static int adp5520_gpio_direction_output(struct gpio_chip *chip, unsigned off, int val) { struct adp5520_gpio *dev; int ret = 0; dev = container_of(chip, struct adp5520_gpio, gpio_chip); set_bit(off, &dev->output); if (val) ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]); else ret |= adp5520_clr_bits(dev->master, ADP5520_GPIO_OUT, dev->lut[off]); ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_CFG_2, dev->lut[off]); return ret; } static int __devinit adp5520_gpio_probe(struct platform_device *pdev) { struct adp5520_gpio_platform_data *pdata = pdev->dev.platform_data; struct adp5520_gpio *dev; struct gpio_chip *gc; int ret, i, gpios; unsigned char ctl_mask = 0; if (pdata == NULL) { dev_err(&pdev->dev, "missing platform data\n"); return -ENODEV; } if (pdev->id != ID_ADP5520) { dev_err(&pdev->dev, "only ADP5520 supports GPIO\n"); return -ENODEV; } dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (dev == NULL) { dev_err(&pdev->dev, "failed to alloc memory\n"); return -ENOMEM; } dev->master = pdev->dev.parent; for (gpios = 0, i = 0; i < ADP5520_MAXGPIOS; i++) if (pdata->gpio_en_mask & (1 << i)) dev->lut[gpios++] = 1 << i; if (gpios < 1) { ret = -EINVAL; goto err; } gc = &dev->gpio_chip; gc->direction_input = adp5520_gpio_direction_input; gc->direction_output = adp5520_gpio_direction_output; gc->get = adp5520_gpio_get_value; gc->set = adp5520_gpio_set_value; gc->can_sleep = 1; gc->base = pdata->gpio_start; gc->ngpio = gpios; gc->label = pdev->name; gc->owner = THIS_MODULE; ret = adp5520_clr_bits(dev->master, ADP5520_GPIO_CFG_1, pdata->gpio_en_mask); if (pdata->gpio_en_mask & ADP5520_GPIO_C3) ctl_mask |= ADP5520_C3_MODE; if (pdata->gpio_en_mask & ADP5520_GPIO_R3) ctl_mask |= ADP5520_R3_MODE; if (ctl_mask) ret = adp5520_set_bits(dev->master, ADP5520_LED_CONTROL, ctl_mask); ret |= adp5520_set_bits(dev->master, ADP5520_GPIO_PULLUP, pdata->gpio_pullup_mask); if (ret) { dev_err(&pdev->dev, "failed to write\n"); goto err; } ret = gpiochip_add(&dev->gpio_chip); if (ret) goto err; platform_set_drvdata(pdev, dev); return 0; err: kfree(dev); return ret; } static int __devexit adp5520_gpio_remove(struct platform_device *pdev) { struct adp5520_gpio *dev; int ret; dev = platform_get_drvdata(pdev); ret = gpiochip_remove(&dev->gpio_chip); if (ret) { dev_err(&pdev->dev, "%s failed, %d\n", "gpiochip_remove()", ret); return ret; } kfree(dev); return 0; } static struct platform_driver adp5520_gpio_driver = { .driver = { .name = "adp5520-gpio", .owner = THIS_MODULE, }, .probe = adp5520_gpio_probe, .remove = __devexit_p(adp5520_gpio_remove), }; static int __init adp5520_gpio_init(void) { return platform_driver_register(&adp5520_gpio_driver); } module_init(adp5520_gpio_init); static void __exit adp5520_gpio_exit(void) { platform_driver_unregister(&adp5520_gpio_driver); } module_exit(adp5520_gpio_exit); MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); MODULE_DESCRIPTION("GPIO ADP5520 Driver"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:adp5520-gpio");