| /* | 
 |  * Qualcomm PMIC8XXX GPIO driver based on RPC | 
 |  * | 
 |  * Copyright (c) 2011, Code Aurora Forum. 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); |