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