18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * GPIO Driver for Dialog DA9052 PMICs.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright(c) 2011 Dialog Semiconductor Ltd.
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Author: David Dajun Chen <dchen@diasemi.com>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include <linux/module.h>
108c2ecf20Sopenharmony_ci#include <linux/fs.h>
118c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
148c2ecf20Sopenharmony_ci#include <linux/syscalls.h>
158c2ecf20Sopenharmony_ci#include <linux/seq_file.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci#include <linux/mfd/da9052/da9052.h>
188c2ecf20Sopenharmony_ci#include <linux/mfd/da9052/reg.h>
198c2ecf20Sopenharmony_ci#include <linux/mfd/da9052/pdata.h>
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define DA9052_INPUT				1
228c2ecf20Sopenharmony_ci#define DA9052_OUTPUT_OPENDRAIN		2
238c2ecf20Sopenharmony_ci#define DA9052_OUTPUT_PUSHPULL			3
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define DA9052_SUPPLY_VDD_IO1			0
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define DA9052_DEBOUNCING_OFF			0
288c2ecf20Sopenharmony_ci#define DA9052_DEBOUNCING_ON			1
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci#define DA9052_OUTPUT_LOWLEVEL			0
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci#define DA9052_ACTIVE_LOW			0
338c2ecf20Sopenharmony_ci#define DA9052_ACTIVE_HIGH			1
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define DA9052_GPIO_MAX_PORTS_PER_REGISTER	8
368c2ecf20Sopenharmony_ci#define DA9052_GPIO_SHIFT_COUNT(no)		(no%8)
378c2ecf20Sopenharmony_ci#define DA9052_GPIO_MASK_UPPER_NIBBLE		0xF0
388c2ecf20Sopenharmony_ci#define DA9052_GPIO_MASK_LOWER_NIBBLE		0x0F
398c2ecf20Sopenharmony_ci#define DA9052_GPIO_NIBBLE_SHIFT		4
408c2ecf20Sopenharmony_ci#define DA9052_IRQ_GPI0			16
418c2ecf20Sopenharmony_ci#define DA9052_GPIO_ODD_SHIFT			7
428c2ecf20Sopenharmony_ci#define DA9052_GPIO_EVEN_SHIFT			3
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistruct da9052_gpio {
458c2ecf20Sopenharmony_ci	struct da9052 *da9052;
468c2ecf20Sopenharmony_ci	struct gpio_chip gp;
478c2ecf20Sopenharmony_ci};
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic unsigned char da9052_gpio_port_odd(unsigned offset)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	return offset % 2;
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic int da9052_gpio_get(struct gpio_chip *gc, unsigned offset)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	struct da9052_gpio *gpio = gpiochip_get_data(gc);
578c2ecf20Sopenharmony_ci	int da9052_port_direction = 0;
588c2ecf20Sopenharmony_ci	int ret;
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	ret = da9052_reg_read(gpio->da9052,
618c2ecf20Sopenharmony_ci			      DA9052_GPIO_0_1_REG + (offset >> 1));
628c2ecf20Sopenharmony_ci	if (ret < 0)
638c2ecf20Sopenharmony_ci		return ret;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	if (da9052_gpio_port_odd(offset)) {
668c2ecf20Sopenharmony_ci		da9052_port_direction = ret & DA9052_GPIO_ODD_PORT_PIN;
678c2ecf20Sopenharmony_ci		da9052_port_direction >>= 4;
688c2ecf20Sopenharmony_ci	} else {
698c2ecf20Sopenharmony_ci		da9052_port_direction = ret & DA9052_GPIO_EVEN_PORT_PIN;
708c2ecf20Sopenharmony_ci	}
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	switch (da9052_port_direction) {
738c2ecf20Sopenharmony_ci	case DA9052_INPUT:
748c2ecf20Sopenharmony_ci		if (offset < DA9052_GPIO_MAX_PORTS_PER_REGISTER)
758c2ecf20Sopenharmony_ci			ret = da9052_reg_read(gpio->da9052,
768c2ecf20Sopenharmony_ci					      DA9052_STATUS_C_REG);
778c2ecf20Sopenharmony_ci		else
788c2ecf20Sopenharmony_ci			ret = da9052_reg_read(gpio->da9052,
798c2ecf20Sopenharmony_ci					      DA9052_STATUS_D_REG);
808c2ecf20Sopenharmony_ci		if (ret < 0)
818c2ecf20Sopenharmony_ci			return ret;
828c2ecf20Sopenharmony_ci		return !!(ret & (1 << DA9052_GPIO_SHIFT_COUNT(offset)));
838c2ecf20Sopenharmony_ci	case DA9052_OUTPUT_PUSHPULL:
848c2ecf20Sopenharmony_ci		if (da9052_gpio_port_odd(offset))
858c2ecf20Sopenharmony_ci			return !!(ret & DA9052_GPIO_ODD_PORT_MODE);
868c2ecf20Sopenharmony_ci		else
878c2ecf20Sopenharmony_ci			return !!(ret & DA9052_GPIO_EVEN_PORT_MODE);
888c2ecf20Sopenharmony_ci	default:
898c2ecf20Sopenharmony_ci		return -EINVAL;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic void da9052_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct da9052_gpio *gpio = gpiochip_get_data(gc);
968c2ecf20Sopenharmony_ci	int ret;
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	if (da9052_gpio_port_odd(offset)) {
998c2ecf20Sopenharmony_ci			ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
1008c2ecf20Sopenharmony_ci						DA9052_GPIO_0_1_REG,
1018c2ecf20Sopenharmony_ci						DA9052_GPIO_ODD_PORT_MODE,
1028c2ecf20Sopenharmony_ci						value << DA9052_GPIO_ODD_SHIFT);
1038c2ecf20Sopenharmony_ci			if (ret != 0)
1048c2ecf20Sopenharmony_ci				dev_err(gpio->da9052->dev,
1058c2ecf20Sopenharmony_ci					"Failed to updated gpio odd reg,%d",
1068c2ecf20Sopenharmony_ci					ret);
1078c2ecf20Sopenharmony_ci	} else {
1088c2ecf20Sopenharmony_ci			ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
1098c2ecf20Sopenharmony_ci						DA9052_GPIO_0_1_REG,
1108c2ecf20Sopenharmony_ci						DA9052_GPIO_EVEN_PORT_MODE,
1118c2ecf20Sopenharmony_ci						value << DA9052_GPIO_EVEN_SHIFT);
1128c2ecf20Sopenharmony_ci			if (ret != 0)
1138c2ecf20Sopenharmony_ci				dev_err(gpio->da9052->dev,
1148c2ecf20Sopenharmony_ci					"Failed to updated gpio even reg,%d",
1158c2ecf20Sopenharmony_ci					ret);
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic int da9052_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	struct da9052_gpio *gpio = gpiochip_get_data(gc);
1228c2ecf20Sopenharmony_ci	unsigned char register_value;
1238c2ecf20Sopenharmony_ci	int ret;
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	/* Format: function - 2 bits type - 1 bit mode - 1 bit */
1268c2ecf20Sopenharmony_ci	register_value = DA9052_INPUT | DA9052_ACTIVE_LOW << 2 |
1278c2ecf20Sopenharmony_ci			 DA9052_DEBOUNCING_ON << 3;
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	if (da9052_gpio_port_odd(offset))
1308c2ecf20Sopenharmony_ci		ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
1318c2ecf20Sopenharmony_ci					DA9052_GPIO_0_1_REG,
1328c2ecf20Sopenharmony_ci					DA9052_GPIO_MASK_UPPER_NIBBLE,
1338c2ecf20Sopenharmony_ci					(register_value <<
1348c2ecf20Sopenharmony_ci					DA9052_GPIO_NIBBLE_SHIFT));
1358c2ecf20Sopenharmony_ci	else
1368c2ecf20Sopenharmony_ci		ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
1378c2ecf20Sopenharmony_ci					DA9052_GPIO_0_1_REG,
1388c2ecf20Sopenharmony_ci					DA9052_GPIO_MASK_LOWER_NIBBLE,
1398c2ecf20Sopenharmony_ci					register_value);
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	return ret;
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_cistatic int da9052_gpio_direction_output(struct gpio_chip *gc,
1458c2ecf20Sopenharmony_ci					unsigned offset, int value)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	struct da9052_gpio *gpio = gpiochip_get_data(gc);
1488c2ecf20Sopenharmony_ci	unsigned char register_value;
1498c2ecf20Sopenharmony_ci	int ret;
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	/* Format: Function - 2 bits Type - 1 bit Mode - 1 bit */
1528c2ecf20Sopenharmony_ci	register_value = DA9052_OUTPUT_PUSHPULL | DA9052_SUPPLY_VDD_IO1 << 2 |
1538c2ecf20Sopenharmony_ci			 value << 3;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	if (da9052_gpio_port_odd(offset))
1568c2ecf20Sopenharmony_ci		ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
1578c2ecf20Sopenharmony_ci					DA9052_GPIO_0_1_REG,
1588c2ecf20Sopenharmony_ci					DA9052_GPIO_MASK_UPPER_NIBBLE,
1598c2ecf20Sopenharmony_ci					(register_value <<
1608c2ecf20Sopenharmony_ci					DA9052_GPIO_NIBBLE_SHIFT));
1618c2ecf20Sopenharmony_ci	else
1628c2ecf20Sopenharmony_ci		ret = da9052_reg_update(gpio->da9052, (offset >> 1) +
1638c2ecf20Sopenharmony_ci					DA9052_GPIO_0_1_REG,
1648c2ecf20Sopenharmony_ci					DA9052_GPIO_MASK_LOWER_NIBBLE,
1658c2ecf20Sopenharmony_ci					register_value);
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	return ret;
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic int da9052_gpio_to_irq(struct gpio_chip *gc, u32 offset)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct da9052_gpio *gpio = gpiochip_get_data(gc);
1738c2ecf20Sopenharmony_ci	struct da9052 *da9052 = gpio->da9052;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	int irq;
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	irq = regmap_irq_get_virq(da9052->irq_data, DA9052_IRQ_GPI0 + offset);
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci	return irq;
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic const struct gpio_chip reference_gp = {
1838c2ecf20Sopenharmony_ci	.label = "da9052-gpio",
1848c2ecf20Sopenharmony_ci	.owner = THIS_MODULE,
1858c2ecf20Sopenharmony_ci	.get = da9052_gpio_get,
1868c2ecf20Sopenharmony_ci	.set = da9052_gpio_set,
1878c2ecf20Sopenharmony_ci	.direction_input = da9052_gpio_direction_input,
1888c2ecf20Sopenharmony_ci	.direction_output = da9052_gpio_direction_output,
1898c2ecf20Sopenharmony_ci	.to_irq = da9052_gpio_to_irq,
1908c2ecf20Sopenharmony_ci	.can_sleep = true,
1918c2ecf20Sopenharmony_ci	.ngpio = 16,
1928c2ecf20Sopenharmony_ci	.base = -1,
1938c2ecf20Sopenharmony_ci};
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic int da9052_gpio_probe(struct platform_device *pdev)
1968c2ecf20Sopenharmony_ci{
1978c2ecf20Sopenharmony_ci	struct da9052_gpio *gpio;
1988c2ecf20Sopenharmony_ci	struct da9052_pdata *pdata;
1998c2ecf20Sopenharmony_ci	int ret;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	gpio = devm_kzalloc(&pdev->dev, sizeof(*gpio), GFP_KERNEL);
2028c2ecf20Sopenharmony_ci	if (!gpio)
2038c2ecf20Sopenharmony_ci		return -ENOMEM;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci	gpio->da9052 = dev_get_drvdata(pdev->dev.parent);
2068c2ecf20Sopenharmony_ci	pdata = dev_get_platdata(gpio->da9052->dev);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	gpio->gp = reference_gp;
2098c2ecf20Sopenharmony_ci	if (pdata && pdata->gpio_base)
2108c2ecf20Sopenharmony_ci		gpio->gp.base = pdata->gpio_base;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	ret = devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio);
2138c2ecf20Sopenharmony_ci	if (ret < 0) {
2148c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret);
2158c2ecf20Sopenharmony_ci		return ret;
2168c2ecf20Sopenharmony_ci	}
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, gpio);
2198c2ecf20Sopenharmony_ci
2208c2ecf20Sopenharmony_ci	return 0;
2218c2ecf20Sopenharmony_ci}
2228c2ecf20Sopenharmony_ci
2238c2ecf20Sopenharmony_cistatic struct platform_driver da9052_gpio_driver = {
2248c2ecf20Sopenharmony_ci	.probe = da9052_gpio_probe,
2258c2ecf20Sopenharmony_ci	.driver = {
2268c2ecf20Sopenharmony_ci		.name	= "da9052-gpio",
2278c2ecf20Sopenharmony_ci	},
2288c2ecf20Sopenharmony_ci};
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_cimodule_platform_driver(da9052_gpio_driver);
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ciMODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
2338c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("DA9052 GPIO Device Driver");
2348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
2358c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:da9052-gpio");
236