| /* |
| * Qualcomm PMIC8XXX GPIO driver based on RPC |
| * |
| * Copyright (c) 2011, The Linux Foundation. All rights reserved. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| */ |
| |
| #define pr_fmt(fmt) "%s: " fmt, __func__ |
| |
| #include <linux/platform_device.h> |
| #include <linux/gpio.h> |
| #include <linux/gpio-pm8xxx-rpc.h> |
| #include <linux/uaccess.h> |
| #include <linux/fs.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <mach/pmic.h> |
| |
| struct pm8xxx_gpio_rpc_chip { |
| struct list_head link; |
| struct gpio_chip gpio_chip; |
| }; |
| |
| static LIST_HEAD(pm8xxx_gpio_rpc_chips); |
| static DEFINE_MUTEX(pm8xxx_gpio_chips_lock); |
| |
| static int pm8xxx_gpio_rpc_get(struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip, |
| unsigned gpio) |
| { |
| int rc; |
| |
| if (gpio >= pm8xxx_gpio_chip->gpio_chip.ngpio |
| || pm8xxx_gpio_chip == NULL) |
| return -EINVAL; |
| |
| rc = pmic_gpio_get_value(gpio); |
| |
| return rc; |
| } |
| |
| static int pm8xxx_gpio_rpc_set(struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip, |
| unsigned gpio, int value) |
| { |
| int rc; |
| |
| if (gpio >= pm8xxx_gpio_chip->gpio_chip.ngpio || |
| pm8xxx_gpio_chip == NULL) |
| return -EINVAL; |
| |
| rc = pmic_gpio_set_value(gpio, value); |
| |
| return rc; |
| } |
| |
| static int pm8xxx_gpio_rpc_set_direction(struct pm8xxx_gpio_rpc_chip |
| *pm8xxx_gpio_chip, unsigned gpio, int direction) |
| { |
| int rc = 0; |
| |
| if (!direction || pm8xxx_gpio_chip == NULL) |
| return -EINVAL; |
| |
| if (direction == PM_GPIO_DIR_IN) |
| rc = pmic_gpio_direction_input(gpio); |
| else if (direction == PM_GPIO_DIR_OUT) |
| rc = pmic_gpio_direction_output(gpio); |
| |
| return rc; |
| } |
| |
| static int pm8xxx_gpio_rpc_read(struct gpio_chip *gpio_chip, unsigned offset) |
| { |
| struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = |
| dev_get_drvdata(gpio_chip->dev); |
| |
| return pm8xxx_gpio_rpc_get(pm8xxx_gpio_chip, offset); |
| } |
| |
| static void pm8xxx_gpio_rpc_write(struct gpio_chip *gpio_chip, |
| unsigned offset, int val) |
| { |
| struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = |
| dev_get_drvdata(gpio_chip->dev); |
| |
| pm8xxx_gpio_rpc_set(pm8xxx_gpio_chip, offset, !!val); |
| } |
| |
| static int pm8xxx_gpio_rpc_direction_input(struct gpio_chip *gpio_chip, |
| unsigned offset) |
| { |
| struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = |
| dev_get_drvdata(gpio_chip->dev); |
| |
| return pm8xxx_gpio_rpc_set_direction(pm8xxx_gpio_chip, offset, |
| PM_GPIO_DIR_IN); |
| } |
| |
| static int pm8xxx_gpio_rpc_direction_output(struct gpio_chip *gpio_chip, |
| unsigned offset, int val) |
| { |
| int ret = 0; |
| |
| struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = |
| dev_get_drvdata(gpio_chip->dev); |
| |
| ret = pm8xxx_gpio_rpc_set_direction(pm8xxx_gpio_chip, offset, |
| PM_GPIO_DIR_OUT); |
| if (!ret) |
| ret = pm8xxx_gpio_rpc_set(pm8xxx_gpio_chip, offset, !!val); |
| |
| return ret; |
| } |
| |
| static void pm8xxx_gpio_rpc_dbg_show(struct seq_file *s, struct gpio_chip |
| *gpio_chip) |
| { |
| struct pm8xxx_gpio_rpc_chip *pmxx_gpio_chip = |
| dev_get_drvdata(gpio_chip->dev); |
| u8 state, mode; |
| const char *label; |
| int i; |
| |
| for (i = 0; i < gpio_chip->ngpio; i++) { |
| label = gpiochip_is_requested(gpio_chip, i); |
| state = pm8xxx_gpio_rpc_get(pmxx_gpio_chip, i); |
| mode = pmic_gpio_get_direction(i); |
| seq_printf(s, "gpio-%-3d (%-12.12s) %s %s", |
| gpio_chip->base + i, |
| label ? label : " ", mode ? "out" : "in", |
| state ? "hi" : "lo"); |
| seq_printf(s, "\n"); |
| } |
| } |
| |
| static int __devinit pm8xxx_gpio_rpc_probe(struct platform_device *pdev) |
| { |
| int ret; |
| struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip; |
| const struct pm8xxx_gpio_rpc_platform_data *pdata = |
| pdev->dev.platform_data; |
| |
| if (!pdata) { |
| pr_err("missing platform data\n"); |
| return -EINVAL; |
| } |
| |
| pm8xxx_gpio_chip = kzalloc(sizeof(struct pm8xxx_gpio_rpc_chip), |
| GFP_KERNEL); |
| if (!pm8xxx_gpio_chip) { |
| pr_err("Cannot allocate pm8xxx_gpio_chip\n"); |
| return -ENOMEM; |
| } |
| |
| pm8xxx_gpio_chip->gpio_chip.label = "pm8xxx-gpio-rpc"; |
| pm8xxx_gpio_chip->gpio_chip.direction_input = |
| pm8xxx_gpio_rpc_direction_input; |
| pm8xxx_gpio_chip->gpio_chip.direction_output = |
| pm8xxx_gpio_rpc_direction_output; |
| pm8xxx_gpio_chip->gpio_chip.get = pm8xxx_gpio_rpc_read; |
| pm8xxx_gpio_chip->gpio_chip.set = pm8xxx_gpio_rpc_write; |
| pm8xxx_gpio_chip->gpio_chip.dbg_show = pm8xxx_gpio_rpc_dbg_show; |
| pm8xxx_gpio_chip->gpio_chip.ngpio = pdata->ngpios; |
| pm8xxx_gpio_chip->gpio_chip.can_sleep = 1; |
| pm8xxx_gpio_chip->gpio_chip.dev = &pdev->dev; |
| pm8xxx_gpio_chip->gpio_chip.base = pdata->gpio_base; |
| |
| mutex_lock(&pm8xxx_gpio_chips_lock); |
| list_add(&pm8xxx_gpio_chip->link, &pm8xxx_gpio_rpc_chips); |
| mutex_unlock(&pm8xxx_gpio_chips_lock); |
| platform_set_drvdata(pdev, pm8xxx_gpio_chip); |
| |
| ret = gpiochip_add(&pm8xxx_gpio_chip->gpio_chip); |
| if (ret) { |
| pr_err("gpiochip_add failed ret = %d\n", ret); |
| goto reset_drvdata; |
| } |
| |
| pr_info("OK: base=%d, ngpio=%d\n", pm8xxx_gpio_chip->gpio_chip.base, |
| pm8xxx_gpio_chip->gpio_chip.ngpio); |
| |
| return 0; |
| |
| reset_drvdata: |
| mutex_lock(&pm8xxx_gpio_chips_lock); |
| list_del(&pm8xxx_gpio_chip->link); |
| mutex_unlock(&pm8xxx_gpio_chips_lock); |
| platform_set_drvdata(pdev, NULL); |
| kfree(pm8xxx_gpio_chip); |
| mutex_destroy(&pm8xxx_gpio_chips_lock); |
| return ret; |
| } |
| |
| static int __devexit pm8xxx_gpio_rpc_remove(struct platform_device *pdev) |
| { |
| struct pm8xxx_gpio_rpc_chip *pm8xxx_gpio_chip = |
| platform_get_drvdata(pdev); |
| |
| mutex_lock(&pm8xxx_gpio_chips_lock); |
| list_del(&pm8xxx_gpio_chip->link); |
| mutex_unlock(&pm8xxx_gpio_chips_lock); |
| platform_set_drvdata(pdev, NULL); |
| if (gpiochip_remove(&pm8xxx_gpio_chip->gpio_chip)) |
| pr_err("failed to remove gpio chip\n"); |
| kfree(pm8xxx_gpio_chip); |
| mutex_destroy(&pm8xxx_gpio_chips_lock); |
| return 0; |
| } |
| |
| static struct platform_driver pm8xxx_gpio_rpc_driver = { |
| .probe = pm8xxx_gpio_rpc_probe, |
| .remove = __devexit_p(pm8xxx_gpio_rpc_remove), |
| .driver = { |
| .name = PM8XXX_GPIO_DEV_NAME, |
| .owner = THIS_MODULE, |
| }, |
| }; |
| |
| static int __init pm8xxx_gpio_rpc_init(void) |
| { |
| return platform_driver_register(&pm8xxx_gpio_rpc_driver); |
| } |
| postcore_initcall(pm8xxx_gpio_rpc_init); |
| |
| static void __exit pm8xxx_gpio_rpc_exit(void) |
| { |
| platform_driver_unregister(&pm8xxx_gpio_rpc_driver); |
| } |
| module_exit(pm8xxx_gpio_rpc_exit); |
| |
| MODULE_LICENSE("GPL v2"); |
| MODULE_DESCRIPTION("PMIC GPIO driver based on RPC"); |
| MODULE_VERSION("1.0"); |
| MODULE_ALIAS("platform:" PM8XXX_GPIO_DEV_NAME); |