18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Access to GPIOs on TWL4030/TPS659x0 chips 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2006-2007 Texas Instruments, Inc. 68c2ecf20Sopenharmony_ci * Copyright (C) 2006 MontaVista Software, Inc. 78c2ecf20Sopenharmony_ci * 88c2ecf20Sopenharmony_ci * Code re-arranged and cleaned up by: 98c2ecf20Sopenharmony_ci * Syed Mohammed Khasim <x0khasim@ti.com> 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * Initial Code: 128c2ecf20Sopenharmony_ci * Andy Lowe / Nishanth Menon 138c2ecf20Sopenharmony_ci */ 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ci#include <linux/module.h> 168c2ecf20Sopenharmony_ci#include <linux/init.h> 178c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 188c2ecf20Sopenharmony_ci#include <linux/kthread.h> 198c2ecf20Sopenharmony_ci#include <linux/irq.h> 208c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 218c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 228c2ecf20Sopenharmony_ci#include <linux/of.h> 238c2ecf20Sopenharmony_ci#include <linux/irqdomain.h> 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci#include <linux/mfd/twl.h> 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci/* 288c2ecf20Sopenharmony_ci * The GPIO "subchip" supports 18 GPIOs which can be configured as 298c2ecf20Sopenharmony_ci * inputs or outputs, with pullups or pulldowns on each pin. Each 308c2ecf20Sopenharmony_ci * GPIO can trigger interrupts on either or both edges. 318c2ecf20Sopenharmony_ci * 328c2ecf20Sopenharmony_ci * GPIO interrupts can be fed to either of two IRQ lines; this is 338c2ecf20Sopenharmony_ci * intended to support multiple hosts. 348c2ecf20Sopenharmony_ci * 358c2ecf20Sopenharmony_ci * There are also two LED pins used sometimes as output-only GPIOs. 368c2ecf20Sopenharmony_ci */ 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* genirq interfaces are not available to modules */ 398c2ecf20Sopenharmony_ci#ifdef MODULE 408c2ecf20Sopenharmony_ci#define is_module() true 418c2ecf20Sopenharmony_ci#else 428c2ecf20Sopenharmony_ci#define is_module() false 438c2ecf20Sopenharmony_ci#endif 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* GPIO_CTRL Fields */ 468c2ecf20Sopenharmony_ci#define MASK_GPIO_CTRL_GPIO0CD1 BIT(0) 478c2ecf20Sopenharmony_ci#define MASK_GPIO_CTRL_GPIO1CD2 BIT(1) 488c2ecf20Sopenharmony_ci#define MASK_GPIO_CTRL_GPIO_ON BIT(2) 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* Mask for GPIO registers when aggregated into a 32-bit integer */ 518c2ecf20Sopenharmony_ci#define GPIO_32_MASK 0x0003ffff 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistruct gpio_twl4030_priv { 548c2ecf20Sopenharmony_ci struct gpio_chip gpio_chip; 558c2ecf20Sopenharmony_ci struct mutex mutex; 568c2ecf20Sopenharmony_ci int irq_base; 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci /* Bitfields for state caching */ 598c2ecf20Sopenharmony_ci unsigned int usage_count; 608c2ecf20Sopenharmony_ci unsigned int direction; 618c2ecf20Sopenharmony_ci unsigned int out_state; 628c2ecf20Sopenharmony_ci}; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci/*----------------------------------------------------------------------*/ 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci/* 678c2ecf20Sopenharmony_ci * To configure TWL4030 GPIO module registers 688c2ecf20Sopenharmony_ci */ 698c2ecf20Sopenharmony_cistatic inline int gpio_twl4030_write(u8 address, u8 data) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci return twl_i2c_write_u8(TWL4030_MODULE_GPIO, data, address); 728c2ecf20Sopenharmony_ci} 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci/*----------------------------------------------------------------------*/ 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci/* 778c2ecf20Sopenharmony_ci * LED register offsets from TWL_MODULE_LED base 788c2ecf20Sopenharmony_ci * PWMs A and B are dedicated to LEDs A and B, respectively. 798c2ecf20Sopenharmony_ci */ 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci#define TWL4030_LED_LEDEN_REG 0x00 828c2ecf20Sopenharmony_ci#define TWL4030_PWMAON_REG 0x01 838c2ecf20Sopenharmony_ci#define TWL4030_PWMAOFF_REG 0x02 848c2ecf20Sopenharmony_ci#define TWL4030_PWMBON_REG 0x03 858c2ecf20Sopenharmony_ci#define TWL4030_PWMBOFF_REG 0x04 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci/* LEDEN bits */ 888c2ecf20Sopenharmony_ci#define LEDEN_LEDAON BIT(0) 898c2ecf20Sopenharmony_ci#define LEDEN_LEDBON BIT(1) 908c2ecf20Sopenharmony_ci#define LEDEN_LEDAEXT BIT(2) 918c2ecf20Sopenharmony_ci#define LEDEN_LEDBEXT BIT(3) 928c2ecf20Sopenharmony_ci#define LEDEN_LEDAPWM BIT(4) 938c2ecf20Sopenharmony_ci#define LEDEN_LEDBPWM BIT(5) 948c2ecf20Sopenharmony_ci#define LEDEN_PWM_LENGTHA BIT(6) 958c2ecf20Sopenharmony_ci#define LEDEN_PWM_LENGTHB BIT(7) 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci#define PWMxON_LENGTH BIT(7) 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci/*----------------------------------------------------------------------*/ 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci/* 1028c2ecf20Sopenharmony_ci * To read a TWL4030 GPIO module register 1038c2ecf20Sopenharmony_ci */ 1048c2ecf20Sopenharmony_cistatic inline int gpio_twl4030_read(u8 address) 1058c2ecf20Sopenharmony_ci{ 1068c2ecf20Sopenharmony_ci u8 data; 1078c2ecf20Sopenharmony_ci int ret = 0; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci ret = twl_i2c_read_u8(TWL4030_MODULE_GPIO, &data, address); 1108c2ecf20Sopenharmony_ci return (ret < 0) ? ret : data; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_ci/*----------------------------------------------------------------------*/ 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_cistatic u8 cached_leden; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci/* The LED lines are open drain outputs ... a FET pulls to GND, so an 1188c2ecf20Sopenharmony_ci * external pullup is needed. We could also expose the integrated PWM 1198c2ecf20Sopenharmony_ci * as a LED brightness control; we initialize it as "always on". 1208c2ecf20Sopenharmony_ci */ 1218c2ecf20Sopenharmony_cistatic void twl4030_led_set_value(int led, int value) 1228c2ecf20Sopenharmony_ci{ 1238c2ecf20Sopenharmony_ci u8 mask = LEDEN_LEDAON | LEDEN_LEDAPWM; 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_ci if (led) 1268c2ecf20Sopenharmony_ci mask <<= 1; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (value) 1298c2ecf20Sopenharmony_ci cached_leden &= ~mask; 1308c2ecf20Sopenharmony_ci else 1318c2ecf20Sopenharmony_ci cached_leden |= mask; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci WARN_ON_ONCE(twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden, 1348c2ecf20Sopenharmony_ci TWL4030_LED_LEDEN_REG)); 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic int twl4030_set_gpio_direction(int gpio, int is_input) 1388c2ecf20Sopenharmony_ci{ 1398c2ecf20Sopenharmony_ci u8 d_bnk = gpio >> 3; 1408c2ecf20Sopenharmony_ci u8 d_msk = BIT(gpio & 0x7); 1418c2ecf20Sopenharmony_ci u8 reg = 0; 1428c2ecf20Sopenharmony_ci u8 base = REG_GPIODATADIR1 + d_bnk; 1438c2ecf20Sopenharmony_ci int ret = 0; 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci ret = gpio_twl4030_read(base); 1468c2ecf20Sopenharmony_ci if (ret >= 0) { 1478c2ecf20Sopenharmony_ci if (is_input) 1488c2ecf20Sopenharmony_ci reg = ret & ~d_msk; 1498c2ecf20Sopenharmony_ci else 1508c2ecf20Sopenharmony_ci reg = ret | d_msk; 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci ret = gpio_twl4030_write(base, reg); 1538c2ecf20Sopenharmony_ci } 1548c2ecf20Sopenharmony_ci return ret; 1558c2ecf20Sopenharmony_ci} 1568c2ecf20Sopenharmony_ci 1578c2ecf20Sopenharmony_cistatic int twl4030_get_gpio_direction(int gpio) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci u8 d_bnk = gpio >> 3; 1608c2ecf20Sopenharmony_ci u8 d_msk = BIT(gpio & 0x7); 1618c2ecf20Sopenharmony_ci u8 base = REG_GPIODATADIR1 + d_bnk; 1628c2ecf20Sopenharmony_ci int ret = 0; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci ret = gpio_twl4030_read(base); 1658c2ecf20Sopenharmony_ci if (ret < 0) 1668c2ecf20Sopenharmony_ci return ret; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (ret & d_msk) 1698c2ecf20Sopenharmony_ci return GPIO_LINE_DIRECTION_OUT; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci return GPIO_LINE_DIRECTION_IN; 1728c2ecf20Sopenharmony_ci} 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_cistatic int twl4030_set_gpio_dataout(int gpio, int enable) 1758c2ecf20Sopenharmony_ci{ 1768c2ecf20Sopenharmony_ci u8 d_bnk = gpio >> 3; 1778c2ecf20Sopenharmony_ci u8 d_msk = BIT(gpio & 0x7); 1788c2ecf20Sopenharmony_ci u8 base = 0; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci if (enable) 1818c2ecf20Sopenharmony_ci base = REG_SETGPIODATAOUT1 + d_bnk; 1828c2ecf20Sopenharmony_ci else 1838c2ecf20Sopenharmony_ci base = REG_CLEARGPIODATAOUT1 + d_bnk; 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci return gpio_twl4030_write(base, d_msk); 1868c2ecf20Sopenharmony_ci} 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_cistatic int twl4030_get_gpio_datain(int gpio) 1898c2ecf20Sopenharmony_ci{ 1908c2ecf20Sopenharmony_ci u8 d_bnk = gpio >> 3; 1918c2ecf20Sopenharmony_ci u8 d_off = gpio & 0x7; 1928c2ecf20Sopenharmony_ci u8 base = 0; 1938c2ecf20Sopenharmony_ci int ret = 0; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci base = REG_GPIODATAIN1 + d_bnk; 1968c2ecf20Sopenharmony_ci ret = gpio_twl4030_read(base); 1978c2ecf20Sopenharmony_ci if (ret > 0) 1988c2ecf20Sopenharmony_ci ret = (ret >> d_off) & 0x1; 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return ret; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci/*----------------------------------------------------------------------*/ 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_cistatic int twl_request(struct gpio_chip *chip, unsigned offset) 2068c2ecf20Sopenharmony_ci{ 2078c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 2088c2ecf20Sopenharmony_ci int status = 0; 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci /* Support the two LED outputs as output-only GPIOs. */ 2138c2ecf20Sopenharmony_ci if (offset >= TWL4030_GPIO_MAX) { 2148c2ecf20Sopenharmony_ci u8 ledclr_mask = LEDEN_LEDAON | LEDEN_LEDAEXT 2158c2ecf20Sopenharmony_ci | LEDEN_LEDAPWM | LEDEN_PWM_LENGTHA; 2168c2ecf20Sopenharmony_ci u8 reg = TWL4030_PWMAON_REG; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci offset -= TWL4030_GPIO_MAX; 2198c2ecf20Sopenharmony_ci if (offset) { 2208c2ecf20Sopenharmony_ci ledclr_mask <<= 1; 2218c2ecf20Sopenharmony_ci reg = TWL4030_PWMBON_REG; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* initialize PWM to always-drive */ 2258c2ecf20Sopenharmony_ci /* Configure PWM OFF register first */ 2268c2ecf20Sopenharmony_ci status = twl_i2c_write_u8(TWL4030_MODULE_LED, 0x7f, reg + 1); 2278c2ecf20Sopenharmony_ci if (status < 0) 2288c2ecf20Sopenharmony_ci goto done; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci /* Followed by PWM ON register */ 2318c2ecf20Sopenharmony_ci status = twl_i2c_write_u8(TWL4030_MODULE_LED, 0x7f, reg); 2328c2ecf20Sopenharmony_ci if (status < 0) 2338c2ecf20Sopenharmony_ci goto done; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* init LED to not-driven (high) */ 2368c2ecf20Sopenharmony_ci status = twl_i2c_read_u8(TWL4030_MODULE_LED, &cached_leden, 2378c2ecf20Sopenharmony_ci TWL4030_LED_LEDEN_REG); 2388c2ecf20Sopenharmony_ci if (status < 0) 2398c2ecf20Sopenharmony_ci goto done; 2408c2ecf20Sopenharmony_ci cached_leden &= ~ledclr_mask; 2418c2ecf20Sopenharmony_ci status = twl_i2c_write_u8(TWL4030_MODULE_LED, cached_leden, 2428c2ecf20Sopenharmony_ci TWL4030_LED_LEDEN_REG); 2438c2ecf20Sopenharmony_ci if (status < 0) 2448c2ecf20Sopenharmony_ci goto done; 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci status = 0; 2478c2ecf20Sopenharmony_ci goto done; 2488c2ecf20Sopenharmony_ci } 2498c2ecf20Sopenharmony_ci 2508c2ecf20Sopenharmony_ci /* on first use, turn GPIO module "on" */ 2518c2ecf20Sopenharmony_ci if (!priv->usage_count) { 2528c2ecf20Sopenharmony_ci struct twl4030_gpio_platform_data *pdata; 2538c2ecf20Sopenharmony_ci u8 value = MASK_GPIO_CTRL_GPIO_ON; 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci /* optionally have the first two GPIOs switch vMMC1 2568c2ecf20Sopenharmony_ci * and vMMC2 power supplies based on card presence. 2578c2ecf20Sopenharmony_ci */ 2588c2ecf20Sopenharmony_ci pdata = dev_get_platdata(chip->parent); 2598c2ecf20Sopenharmony_ci if (pdata) 2608c2ecf20Sopenharmony_ci value |= pdata->mmc_cd & 0x03; 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_ci status = gpio_twl4030_write(REG_GPIO_CTRL, value); 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cidone: 2668c2ecf20Sopenharmony_ci if (!status) 2678c2ecf20Sopenharmony_ci priv->usage_count |= BIT(offset); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 2708c2ecf20Sopenharmony_ci return status; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_cistatic void twl_free(struct gpio_chip *chip, unsigned offset) 2748c2ecf20Sopenharmony_ci{ 2758c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 2788c2ecf20Sopenharmony_ci if (offset >= TWL4030_GPIO_MAX) { 2798c2ecf20Sopenharmony_ci twl4030_led_set_value(offset - TWL4030_GPIO_MAX, 1); 2808c2ecf20Sopenharmony_ci goto out; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci priv->usage_count &= ~BIT(offset); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci /* on last use, switch off GPIO module */ 2868c2ecf20Sopenharmony_ci if (!priv->usage_count) 2878c2ecf20Sopenharmony_ci gpio_twl4030_write(REG_GPIO_CTRL, 0x0); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ciout: 2908c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 2918c2ecf20Sopenharmony_ci} 2928c2ecf20Sopenharmony_ci 2938c2ecf20Sopenharmony_cistatic int twl_direction_in(struct gpio_chip *chip, unsigned offset) 2948c2ecf20Sopenharmony_ci{ 2958c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 2968c2ecf20Sopenharmony_ci int ret; 2978c2ecf20Sopenharmony_ci 2988c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 2998c2ecf20Sopenharmony_ci if (offset < TWL4030_GPIO_MAX) 3008c2ecf20Sopenharmony_ci ret = twl4030_set_gpio_direction(offset, 1); 3018c2ecf20Sopenharmony_ci else 3028c2ecf20Sopenharmony_ci ret = -EINVAL; /* LED outputs can't be set as input */ 3038c2ecf20Sopenharmony_ci 3048c2ecf20Sopenharmony_ci if (!ret) 3058c2ecf20Sopenharmony_ci priv->direction &= ~BIT(offset); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci return ret; 3108c2ecf20Sopenharmony_ci} 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_cistatic int twl_get(struct gpio_chip *chip, unsigned offset) 3138c2ecf20Sopenharmony_ci{ 3148c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 3158c2ecf20Sopenharmony_ci int ret; 3168c2ecf20Sopenharmony_ci int status = 0; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 3198c2ecf20Sopenharmony_ci if (!(priv->usage_count & BIT(offset))) { 3208c2ecf20Sopenharmony_ci ret = -EPERM; 3218c2ecf20Sopenharmony_ci goto out; 3228c2ecf20Sopenharmony_ci } 3238c2ecf20Sopenharmony_ci 3248c2ecf20Sopenharmony_ci if (priv->direction & BIT(offset)) 3258c2ecf20Sopenharmony_ci status = priv->out_state & BIT(offset); 3268c2ecf20Sopenharmony_ci else 3278c2ecf20Sopenharmony_ci status = twl4030_get_gpio_datain(offset); 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci ret = (status < 0) ? status : !!status; 3308c2ecf20Sopenharmony_ciout: 3318c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3328c2ecf20Sopenharmony_ci return ret; 3338c2ecf20Sopenharmony_ci} 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_cistatic void twl_set(struct gpio_chip *chip, unsigned offset, int value) 3368c2ecf20Sopenharmony_ci{ 3378c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 3388c2ecf20Sopenharmony_ci 3398c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 3408c2ecf20Sopenharmony_ci if (offset < TWL4030_GPIO_MAX) 3418c2ecf20Sopenharmony_ci twl4030_set_gpio_dataout(offset, value); 3428c2ecf20Sopenharmony_ci else 3438c2ecf20Sopenharmony_ci twl4030_led_set_value(offset - TWL4030_GPIO_MAX, value); 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci if (value) 3468c2ecf20Sopenharmony_ci priv->out_state |= BIT(offset); 3478c2ecf20Sopenharmony_ci else 3488c2ecf20Sopenharmony_ci priv->out_state &= ~BIT(offset); 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3518c2ecf20Sopenharmony_ci} 3528c2ecf20Sopenharmony_ci 3538c2ecf20Sopenharmony_cistatic int twl_direction_out(struct gpio_chip *chip, unsigned offset, int value) 3548c2ecf20Sopenharmony_ci{ 3558c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 3568c2ecf20Sopenharmony_ci int ret = 0; 3578c2ecf20Sopenharmony_ci 3588c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 3598c2ecf20Sopenharmony_ci if (offset < TWL4030_GPIO_MAX) { 3608c2ecf20Sopenharmony_ci ret = twl4030_set_gpio_direction(offset, 0); 3618c2ecf20Sopenharmony_ci if (ret) { 3628c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3638c2ecf20Sopenharmony_ci return ret; 3648c2ecf20Sopenharmony_ci } 3658c2ecf20Sopenharmony_ci } 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci /* 3688c2ecf20Sopenharmony_ci * LED gpios i.e. offset >= TWL4030_GPIO_MAX are always output 3698c2ecf20Sopenharmony_ci */ 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci priv->direction |= BIT(offset); 3728c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci twl_set(chip, offset, value); 3758c2ecf20Sopenharmony_ci 3768c2ecf20Sopenharmony_ci return ret; 3778c2ecf20Sopenharmony_ci} 3788c2ecf20Sopenharmony_ci 3798c2ecf20Sopenharmony_cistatic int twl_get_direction(struct gpio_chip *chip, unsigned offset) 3808c2ecf20Sopenharmony_ci{ 3818c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 3828c2ecf20Sopenharmony_ci /* 3838c2ecf20Sopenharmony_ci * Default GPIO_LINE_DIRECTION_OUT 3848c2ecf20Sopenharmony_ci * LED GPIOs >= TWL4030_GPIO_MAX are always output 3858c2ecf20Sopenharmony_ci */ 3868c2ecf20Sopenharmony_ci int ret = GPIO_LINE_DIRECTION_OUT; 3878c2ecf20Sopenharmony_ci 3888c2ecf20Sopenharmony_ci mutex_lock(&priv->mutex); 3898c2ecf20Sopenharmony_ci if (offset < TWL4030_GPIO_MAX) { 3908c2ecf20Sopenharmony_ci ret = twl4030_get_gpio_direction(offset); 3918c2ecf20Sopenharmony_ci if (ret) { 3928c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3938c2ecf20Sopenharmony_ci return ret; 3948c2ecf20Sopenharmony_ci } 3958c2ecf20Sopenharmony_ci } 3968c2ecf20Sopenharmony_ci mutex_unlock(&priv->mutex); 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_ci return ret; 3998c2ecf20Sopenharmony_ci} 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_cistatic int twl_to_irq(struct gpio_chip *chip, unsigned offset) 4028c2ecf20Sopenharmony_ci{ 4038c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = gpiochip_get_data(chip); 4048c2ecf20Sopenharmony_ci 4058c2ecf20Sopenharmony_ci return (priv->irq_base && (offset < TWL4030_GPIO_MAX)) 4068c2ecf20Sopenharmony_ci ? (priv->irq_base + offset) 4078c2ecf20Sopenharmony_ci : -EINVAL; 4088c2ecf20Sopenharmony_ci} 4098c2ecf20Sopenharmony_ci 4108c2ecf20Sopenharmony_cistatic const struct gpio_chip template_chip = { 4118c2ecf20Sopenharmony_ci .label = "twl4030", 4128c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 4138c2ecf20Sopenharmony_ci .request = twl_request, 4148c2ecf20Sopenharmony_ci .free = twl_free, 4158c2ecf20Sopenharmony_ci .direction_input = twl_direction_in, 4168c2ecf20Sopenharmony_ci .direction_output = twl_direction_out, 4178c2ecf20Sopenharmony_ci .get_direction = twl_get_direction, 4188c2ecf20Sopenharmony_ci .get = twl_get, 4198c2ecf20Sopenharmony_ci .set = twl_set, 4208c2ecf20Sopenharmony_ci .to_irq = twl_to_irq, 4218c2ecf20Sopenharmony_ci .can_sleep = true, 4228c2ecf20Sopenharmony_ci}; 4238c2ecf20Sopenharmony_ci 4248c2ecf20Sopenharmony_ci/*----------------------------------------------------------------------*/ 4258c2ecf20Sopenharmony_ci 4268c2ecf20Sopenharmony_cistatic int gpio_twl4030_pulls(u32 ups, u32 downs) 4278c2ecf20Sopenharmony_ci{ 4288c2ecf20Sopenharmony_ci u8 message[5]; 4298c2ecf20Sopenharmony_ci unsigned i, gpio_bit; 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci /* For most pins, a pulldown was enabled by default. 4328c2ecf20Sopenharmony_ci * We should have data that's specific to this board. 4338c2ecf20Sopenharmony_ci */ 4348c2ecf20Sopenharmony_ci for (gpio_bit = 1, i = 0; i < 5; i++) { 4358c2ecf20Sopenharmony_ci u8 bit_mask; 4368c2ecf20Sopenharmony_ci unsigned j; 4378c2ecf20Sopenharmony_ci 4388c2ecf20Sopenharmony_ci for (bit_mask = 0, j = 0; j < 8; j += 2, gpio_bit <<= 1) { 4398c2ecf20Sopenharmony_ci if (ups & gpio_bit) 4408c2ecf20Sopenharmony_ci bit_mask |= 1 << (j + 1); 4418c2ecf20Sopenharmony_ci else if (downs & gpio_bit) 4428c2ecf20Sopenharmony_ci bit_mask |= 1 << (j + 0); 4438c2ecf20Sopenharmony_ci } 4448c2ecf20Sopenharmony_ci message[i] = bit_mask; 4458c2ecf20Sopenharmony_ci } 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_ci return twl_i2c_write(TWL4030_MODULE_GPIO, message, 4488c2ecf20Sopenharmony_ci REG_GPIOPUPDCTR1, 5); 4498c2ecf20Sopenharmony_ci} 4508c2ecf20Sopenharmony_ci 4518c2ecf20Sopenharmony_cistatic int gpio_twl4030_debounce(u32 debounce, u8 mmc_cd) 4528c2ecf20Sopenharmony_ci{ 4538c2ecf20Sopenharmony_ci u8 message[3]; 4548c2ecf20Sopenharmony_ci 4558c2ecf20Sopenharmony_ci /* 30 msec of debouncing is always used for MMC card detect, 4568c2ecf20Sopenharmony_ci * and is optional for everything else. 4578c2ecf20Sopenharmony_ci */ 4588c2ecf20Sopenharmony_ci message[0] = (debounce & 0xff) | (mmc_cd & 0x03); 4598c2ecf20Sopenharmony_ci debounce >>= 8; 4608c2ecf20Sopenharmony_ci message[1] = (debounce & 0xff); 4618c2ecf20Sopenharmony_ci debounce >>= 8; 4628c2ecf20Sopenharmony_ci message[2] = (debounce & 0x03); 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci return twl_i2c_write(TWL4030_MODULE_GPIO, message, 4658c2ecf20Sopenharmony_ci REG_GPIO_DEBEN1, 3); 4668c2ecf20Sopenharmony_ci} 4678c2ecf20Sopenharmony_ci 4688c2ecf20Sopenharmony_cistatic int gpio_twl4030_remove(struct platform_device *pdev); 4698c2ecf20Sopenharmony_ci 4708c2ecf20Sopenharmony_cistatic struct twl4030_gpio_platform_data *of_gpio_twl4030(struct device *dev, 4718c2ecf20Sopenharmony_ci struct twl4030_gpio_platform_data *pdata) 4728c2ecf20Sopenharmony_ci{ 4738c2ecf20Sopenharmony_ci struct twl4030_gpio_platform_data *omap_twl_info; 4748c2ecf20Sopenharmony_ci 4758c2ecf20Sopenharmony_ci omap_twl_info = devm_kzalloc(dev, sizeof(*omap_twl_info), GFP_KERNEL); 4768c2ecf20Sopenharmony_ci if (!omap_twl_info) 4778c2ecf20Sopenharmony_ci return NULL; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci if (pdata) 4808c2ecf20Sopenharmony_ci *omap_twl_info = *pdata; 4818c2ecf20Sopenharmony_ci 4828c2ecf20Sopenharmony_ci omap_twl_info->use_leds = of_property_read_bool(dev->of_node, 4838c2ecf20Sopenharmony_ci "ti,use-leds"); 4848c2ecf20Sopenharmony_ci 4858c2ecf20Sopenharmony_ci of_property_read_u32(dev->of_node, "ti,debounce", 4868c2ecf20Sopenharmony_ci &omap_twl_info->debounce); 4878c2ecf20Sopenharmony_ci of_property_read_u32(dev->of_node, "ti,mmc-cd", 4888c2ecf20Sopenharmony_ci (u32 *)&omap_twl_info->mmc_cd); 4898c2ecf20Sopenharmony_ci of_property_read_u32(dev->of_node, "ti,pullups", 4908c2ecf20Sopenharmony_ci &omap_twl_info->pullups); 4918c2ecf20Sopenharmony_ci of_property_read_u32(dev->of_node, "ti,pulldowns", 4928c2ecf20Sopenharmony_ci &omap_twl_info->pulldowns); 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci return omap_twl_info; 4958c2ecf20Sopenharmony_ci} 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_cistatic int gpio_twl4030_probe(struct platform_device *pdev) 4988c2ecf20Sopenharmony_ci{ 4998c2ecf20Sopenharmony_ci struct twl4030_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev); 5008c2ecf20Sopenharmony_ci struct device_node *node = pdev->dev.of_node; 5018c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv; 5028c2ecf20Sopenharmony_ci int ret, irq_base; 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(struct gpio_twl4030_priv), 5058c2ecf20Sopenharmony_ci GFP_KERNEL); 5068c2ecf20Sopenharmony_ci if (!priv) 5078c2ecf20Sopenharmony_ci return -ENOMEM; 5088c2ecf20Sopenharmony_ci 5098c2ecf20Sopenharmony_ci /* maybe setup IRQs */ 5108c2ecf20Sopenharmony_ci if (is_module()) { 5118c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "can't dispatch IRQs from modules\n"); 5128c2ecf20Sopenharmony_ci goto no_irqs; 5138c2ecf20Sopenharmony_ci } 5148c2ecf20Sopenharmony_ci 5158c2ecf20Sopenharmony_ci irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 5168c2ecf20Sopenharmony_ci 0, TWL4030_GPIO_MAX, 0); 5178c2ecf20Sopenharmony_ci if (irq_base < 0) { 5188c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to alloc irq_descs\n"); 5198c2ecf20Sopenharmony_ci return irq_base; 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci irq_domain_add_legacy(node, TWL4030_GPIO_MAX, irq_base, 0, 5238c2ecf20Sopenharmony_ci &irq_domain_simple_ops, NULL); 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_ci ret = twl4030_sih_setup(&pdev->dev, TWL4030_MODULE_GPIO, irq_base); 5268c2ecf20Sopenharmony_ci if (ret < 0) 5278c2ecf20Sopenharmony_ci return ret; 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci priv->irq_base = irq_base; 5308c2ecf20Sopenharmony_ci 5318c2ecf20Sopenharmony_cino_irqs: 5328c2ecf20Sopenharmony_ci priv->gpio_chip = template_chip; 5338c2ecf20Sopenharmony_ci priv->gpio_chip.base = -1; 5348c2ecf20Sopenharmony_ci priv->gpio_chip.ngpio = TWL4030_GPIO_MAX; 5358c2ecf20Sopenharmony_ci priv->gpio_chip.parent = &pdev->dev; 5368c2ecf20Sopenharmony_ci 5378c2ecf20Sopenharmony_ci mutex_init(&priv->mutex); 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci if (node) 5408c2ecf20Sopenharmony_ci pdata = of_gpio_twl4030(&pdev->dev, pdata); 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ci if (pdata == NULL) { 5438c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Platform data is missing\n"); 5448c2ecf20Sopenharmony_ci return -ENXIO; 5458c2ecf20Sopenharmony_ci } 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_ci /* 5488c2ecf20Sopenharmony_ci * NOTE: boards may waste power if they don't set pullups 5498c2ecf20Sopenharmony_ci * and pulldowns correctly ... default for non-ULPI pins is 5508c2ecf20Sopenharmony_ci * pulldown, and some other pins may have external pullups 5518c2ecf20Sopenharmony_ci * or pulldowns. Careful! 5528c2ecf20Sopenharmony_ci */ 5538c2ecf20Sopenharmony_ci ret = gpio_twl4030_pulls(pdata->pullups, pdata->pulldowns); 5548c2ecf20Sopenharmony_ci if (ret) 5558c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "pullups %.05x %.05x --> %d\n", 5568c2ecf20Sopenharmony_ci pdata->pullups, pdata->pulldowns, ret); 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci ret = gpio_twl4030_debounce(pdata->debounce, pdata->mmc_cd); 5598c2ecf20Sopenharmony_ci if (ret) 5608c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "debounce %.03x %.01x --> %d\n", 5618c2ecf20Sopenharmony_ci pdata->debounce, pdata->mmc_cd, ret); 5628c2ecf20Sopenharmony_ci 5638c2ecf20Sopenharmony_ci /* 5648c2ecf20Sopenharmony_ci * NOTE: we assume VIBRA_CTL.VIBRA_EN, in MODULE_AUDIO_VOICE, 5658c2ecf20Sopenharmony_ci * is (still) clear if use_leds is set. 5668c2ecf20Sopenharmony_ci */ 5678c2ecf20Sopenharmony_ci if (pdata->use_leds) 5688c2ecf20Sopenharmony_ci priv->gpio_chip.ngpio += 2; 5698c2ecf20Sopenharmony_ci 5708c2ecf20Sopenharmony_ci ret = gpiochip_add_data(&priv->gpio_chip, priv); 5718c2ecf20Sopenharmony_ci if (ret < 0) { 5728c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "could not register gpiochip, %d\n", ret); 5738c2ecf20Sopenharmony_ci priv->gpio_chip.ngpio = 0; 5748c2ecf20Sopenharmony_ci gpio_twl4030_remove(pdev); 5758c2ecf20Sopenharmony_ci goto out; 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 5798c2ecf20Sopenharmony_ci 5808c2ecf20Sopenharmony_ci if (pdata->setup) { 5818c2ecf20Sopenharmony_ci int status; 5828c2ecf20Sopenharmony_ci 5838c2ecf20Sopenharmony_ci status = pdata->setup(&pdev->dev, priv->gpio_chip.base, 5848c2ecf20Sopenharmony_ci TWL4030_GPIO_MAX); 5858c2ecf20Sopenharmony_ci if (status) 5868c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "setup --> %d\n", status); 5878c2ecf20Sopenharmony_ci } 5888c2ecf20Sopenharmony_ci 5898c2ecf20Sopenharmony_ciout: 5908c2ecf20Sopenharmony_ci return ret; 5918c2ecf20Sopenharmony_ci} 5928c2ecf20Sopenharmony_ci 5938c2ecf20Sopenharmony_ci/* Cannot use as gpio_twl4030_probe() calls us */ 5948c2ecf20Sopenharmony_cistatic int gpio_twl4030_remove(struct platform_device *pdev) 5958c2ecf20Sopenharmony_ci{ 5968c2ecf20Sopenharmony_ci struct twl4030_gpio_platform_data *pdata = dev_get_platdata(&pdev->dev); 5978c2ecf20Sopenharmony_ci struct gpio_twl4030_priv *priv = platform_get_drvdata(pdev); 5988c2ecf20Sopenharmony_ci int status; 5998c2ecf20Sopenharmony_ci 6008c2ecf20Sopenharmony_ci if (pdata && pdata->teardown) { 6018c2ecf20Sopenharmony_ci status = pdata->teardown(&pdev->dev, priv->gpio_chip.base, 6028c2ecf20Sopenharmony_ci TWL4030_GPIO_MAX); 6038c2ecf20Sopenharmony_ci if (status) { 6048c2ecf20Sopenharmony_ci dev_dbg(&pdev->dev, "teardown --> %d\n", status); 6058c2ecf20Sopenharmony_ci return status; 6068c2ecf20Sopenharmony_ci } 6078c2ecf20Sopenharmony_ci } 6088c2ecf20Sopenharmony_ci 6098c2ecf20Sopenharmony_ci gpiochip_remove(&priv->gpio_chip); 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_ci if (is_module()) 6128c2ecf20Sopenharmony_ci return 0; 6138c2ecf20Sopenharmony_ci 6148c2ecf20Sopenharmony_ci /* REVISIT no support yet for deregistering all the IRQs */ 6158c2ecf20Sopenharmony_ci WARN_ON(1); 6168c2ecf20Sopenharmony_ci return -EIO; 6178c2ecf20Sopenharmony_ci} 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_cistatic const struct of_device_id twl_gpio_match[] = { 6208c2ecf20Sopenharmony_ci { .compatible = "ti,twl4030-gpio", }, 6218c2ecf20Sopenharmony_ci { }, 6228c2ecf20Sopenharmony_ci}; 6238c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, twl_gpio_match); 6248c2ecf20Sopenharmony_ci 6258c2ecf20Sopenharmony_ci/* Note: this hardware lives inside an I2C-based multi-function device. */ 6268c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:twl4030_gpio"); 6278c2ecf20Sopenharmony_ci 6288c2ecf20Sopenharmony_cistatic struct platform_driver gpio_twl4030_driver = { 6298c2ecf20Sopenharmony_ci .driver = { 6308c2ecf20Sopenharmony_ci .name = "twl4030_gpio", 6318c2ecf20Sopenharmony_ci .of_match_table = twl_gpio_match, 6328c2ecf20Sopenharmony_ci }, 6338c2ecf20Sopenharmony_ci .probe = gpio_twl4030_probe, 6348c2ecf20Sopenharmony_ci .remove = gpio_twl4030_remove, 6358c2ecf20Sopenharmony_ci}; 6368c2ecf20Sopenharmony_ci 6378c2ecf20Sopenharmony_cistatic int __init gpio_twl4030_init(void) 6388c2ecf20Sopenharmony_ci{ 6398c2ecf20Sopenharmony_ci return platform_driver_register(&gpio_twl4030_driver); 6408c2ecf20Sopenharmony_ci} 6418c2ecf20Sopenharmony_cisubsys_initcall(gpio_twl4030_init); 6428c2ecf20Sopenharmony_ci 6438c2ecf20Sopenharmony_cistatic void __exit gpio_twl4030_exit(void) 6448c2ecf20Sopenharmony_ci{ 6458c2ecf20Sopenharmony_ci platform_driver_unregister(&gpio_twl4030_driver); 6468c2ecf20Sopenharmony_ci} 6478c2ecf20Sopenharmony_cimodule_exit(gpio_twl4030_exit); 6488c2ecf20Sopenharmony_ci 6498c2ecf20Sopenharmony_ciMODULE_AUTHOR("Texas Instruments, Inc."); 6508c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO interface for TWL4030"); 6518c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 652