| /* |
| * 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 adp5520_gpio_probe(struct platform_device *pdev) |
| { |
| struct adp5520_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev); |
| 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 = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); |
| if (dev == NULL) |
| 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 = true; |
| |
| 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: |
| return ret; |
| } |
| |
| static int 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; |
| } |
| |
| return 0; |
| } |
| |
| static struct platform_driver adp5520_gpio_driver = { |
| .driver = { |
| .name = "adp5520-gpio", |
| .owner = THIS_MODULE, |
| }, |
| .probe = adp5520_gpio_probe, |
| .remove = 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"); |