|  | /* | 
|  | * 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), | 
|  | }; | 
|  |  | 
|  | module_platform_driver(adp5520_gpio_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); | 
|  | MODULE_DESCRIPTION("GPIO ADP5520 Driver"); | 
|  | MODULE_LICENSE("GPL"); | 
|  | MODULE_ALIAS("platform:adp5520-gpio"); |