18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+ 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * gpiolib support for Wolfson WM831x PMICs 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2009 Wolfson Microelectronics PLC. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 88c2ecf20Sopenharmony_ci * 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/kernel.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/module.h> 148c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 158c2ecf20Sopenharmony_ci#include <linux/mfd/core.h> 168c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 178c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#include <linux/mfd/wm831x/core.h> 208c2ecf20Sopenharmony_ci#include <linux/mfd/wm831x/pdata.h> 218c2ecf20Sopenharmony_ci#include <linux/mfd/wm831x/gpio.h> 228c2ecf20Sopenharmony_ci#include <linux/mfd/wm831x/irq.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_cistruct wm831x_gpio { 258c2ecf20Sopenharmony_ci struct wm831x *wm831x; 268c2ecf20Sopenharmony_ci struct gpio_chip gpio_chip; 278c2ecf20Sopenharmony_ci}; 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_cistatic int wm831x_gpio_direction_in(struct gpio_chip *chip, unsigned offset) 308c2ecf20Sopenharmony_ci{ 318c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 328c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 338c2ecf20Sopenharmony_ci int val = WM831X_GPN_DIR; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci if (wm831x->has_gpio_ena) 368c2ecf20Sopenharmony_ci val |= WM831X_GPN_TRI; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci return wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, 398c2ecf20Sopenharmony_ci WM831X_GPN_DIR | WM831X_GPN_TRI | 408c2ecf20Sopenharmony_ci WM831X_GPN_FN_MASK, val); 418c2ecf20Sopenharmony_ci} 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic int wm831x_gpio_get(struct gpio_chip *chip, unsigned offset) 448c2ecf20Sopenharmony_ci{ 458c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 468c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 478c2ecf20Sopenharmony_ci int ret; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci ret = wm831x_reg_read(wm831x, WM831X_GPIO_LEVEL); 508c2ecf20Sopenharmony_ci if (ret < 0) 518c2ecf20Sopenharmony_ci return ret; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci if (ret & 1 << offset) 548c2ecf20Sopenharmony_ci return 1; 558c2ecf20Sopenharmony_ci else 568c2ecf20Sopenharmony_ci return 0; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_cistatic void wm831x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) 608c2ecf20Sopenharmony_ci{ 618c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 628c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci wm831x_set_bits(wm831x, WM831X_GPIO_LEVEL, 1 << offset, 658c2ecf20Sopenharmony_ci value << offset); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic int wm831x_gpio_direction_out(struct gpio_chip *chip, 698c2ecf20Sopenharmony_ci unsigned offset, int value) 708c2ecf20Sopenharmony_ci{ 718c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 728c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 738c2ecf20Sopenharmony_ci int val = 0; 748c2ecf20Sopenharmony_ci int ret; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (wm831x->has_gpio_ena) 778c2ecf20Sopenharmony_ci val |= WM831X_GPN_TRI; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci ret = wm831x_set_bits(wm831x, WM831X_GPIO1_CONTROL + offset, 808c2ecf20Sopenharmony_ci WM831X_GPN_DIR | WM831X_GPN_TRI | 818c2ecf20Sopenharmony_ci WM831X_GPN_FN_MASK, val); 828c2ecf20Sopenharmony_ci if (ret < 0) 838c2ecf20Sopenharmony_ci return ret; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci /* Can only set GPIO state once it's in output mode */ 868c2ecf20Sopenharmony_ci wm831x_gpio_set(chip, offset, value); 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic int wm831x_gpio_to_irq(struct gpio_chip *chip, unsigned offset) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 948c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci return irq_create_mapping(wm831x->irq_domain, 978c2ecf20Sopenharmony_ci WM831X_IRQ_GPIO_1 + offset); 988c2ecf20Sopenharmony_ci} 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_cistatic int wm831x_gpio_set_debounce(struct wm831x *wm831x, unsigned offset, 1018c2ecf20Sopenharmony_ci unsigned debounce) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci int reg = WM831X_GPIO1_CONTROL + offset; 1048c2ecf20Sopenharmony_ci int ret, fn; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci ret = wm831x_reg_read(wm831x, reg); 1078c2ecf20Sopenharmony_ci if (ret < 0) 1088c2ecf20Sopenharmony_ci return ret; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci switch (ret & WM831X_GPN_FN_MASK) { 1118c2ecf20Sopenharmony_ci case 0: 1128c2ecf20Sopenharmony_ci case 1: 1138c2ecf20Sopenharmony_ci break; 1148c2ecf20Sopenharmony_ci default: 1158c2ecf20Sopenharmony_ci /* Not in GPIO mode */ 1168c2ecf20Sopenharmony_ci return -EBUSY; 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (debounce >= 32 && debounce <= 64) 1208c2ecf20Sopenharmony_ci fn = 0; 1218c2ecf20Sopenharmony_ci else if (debounce >= 4000 && debounce <= 8000) 1228c2ecf20Sopenharmony_ci fn = 1; 1238c2ecf20Sopenharmony_ci else 1248c2ecf20Sopenharmony_ci return -EINVAL; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci return wm831x_set_bits(wm831x, reg, WM831X_GPN_FN_MASK, fn); 1278c2ecf20Sopenharmony_ci} 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_cistatic int wm831x_set_config(struct gpio_chip *chip, unsigned int offset, 1308c2ecf20Sopenharmony_ci unsigned long config) 1318c2ecf20Sopenharmony_ci{ 1328c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 1338c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 1348c2ecf20Sopenharmony_ci int reg = WM831X_GPIO1_CONTROL + offset; 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_ci switch (pinconf_to_config_param(config)) { 1378c2ecf20Sopenharmony_ci case PIN_CONFIG_DRIVE_OPEN_DRAIN: 1388c2ecf20Sopenharmony_ci return wm831x_set_bits(wm831x, reg, 1398c2ecf20Sopenharmony_ci WM831X_GPN_OD_MASK, WM831X_GPN_OD); 1408c2ecf20Sopenharmony_ci case PIN_CONFIG_DRIVE_PUSH_PULL: 1418c2ecf20Sopenharmony_ci return wm831x_set_bits(wm831x, reg, 1428c2ecf20Sopenharmony_ci WM831X_GPN_OD_MASK, 0); 1438c2ecf20Sopenharmony_ci case PIN_CONFIG_INPUT_DEBOUNCE: 1448c2ecf20Sopenharmony_ci return wm831x_gpio_set_debounce(wm831x, offset, 1458c2ecf20Sopenharmony_ci pinconf_to_config_argument(config)); 1468c2ecf20Sopenharmony_ci default: 1478c2ecf20Sopenharmony_ci break; 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ci return -ENOTSUPP; 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci#ifdef CONFIG_DEBUG_FS 1548c2ecf20Sopenharmony_cistatic void wm831x_gpio_dbg_show(struct seq_file *s, struct gpio_chip *chip) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio = gpiochip_get_data(chip); 1578c2ecf20Sopenharmony_ci struct wm831x *wm831x = wm831x_gpio->wm831x; 1588c2ecf20Sopenharmony_ci int i, tristated; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci for (i = 0; i < chip->ngpio; i++) { 1618c2ecf20Sopenharmony_ci int gpio = i + chip->base; 1628c2ecf20Sopenharmony_ci int reg; 1638c2ecf20Sopenharmony_ci const char *label, *pull, *powerdomain; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci /* We report the GPIO even if it's not requested since 1668c2ecf20Sopenharmony_ci * we're also reporting things like alternate 1678c2ecf20Sopenharmony_ci * functions which apply even when the GPIO is not in 1688c2ecf20Sopenharmony_ci * use as a GPIO. 1698c2ecf20Sopenharmony_ci */ 1708c2ecf20Sopenharmony_ci label = gpiochip_is_requested(chip, i); 1718c2ecf20Sopenharmony_ci if (!label) 1728c2ecf20Sopenharmony_ci label = "Unrequested"; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci seq_printf(s, " gpio-%-3d (%-20.20s) ", gpio, label); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci reg = wm831x_reg_read(wm831x, WM831X_GPIO1_CONTROL + i); 1778c2ecf20Sopenharmony_ci if (reg < 0) { 1788c2ecf20Sopenharmony_ci dev_err(wm831x->dev, 1798c2ecf20Sopenharmony_ci "GPIO control %d read failed: %d\n", 1808c2ecf20Sopenharmony_ci gpio, reg); 1818c2ecf20Sopenharmony_ci seq_putc(s, '\n'); 1828c2ecf20Sopenharmony_ci continue; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci switch (reg & WM831X_GPN_PULL_MASK) { 1868c2ecf20Sopenharmony_ci case WM831X_GPIO_PULL_NONE: 1878c2ecf20Sopenharmony_ci pull = "nopull"; 1888c2ecf20Sopenharmony_ci break; 1898c2ecf20Sopenharmony_ci case WM831X_GPIO_PULL_DOWN: 1908c2ecf20Sopenharmony_ci pull = "pulldown"; 1918c2ecf20Sopenharmony_ci break; 1928c2ecf20Sopenharmony_ci case WM831X_GPIO_PULL_UP: 1938c2ecf20Sopenharmony_ci pull = "pullup"; 1948c2ecf20Sopenharmony_ci break; 1958c2ecf20Sopenharmony_ci default: 1968c2ecf20Sopenharmony_ci pull = "INVALID PULL"; 1978c2ecf20Sopenharmony_ci break; 1988c2ecf20Sopenharmony_ci } 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci switch (i + 1) { 2018c2ecf20Sopenharmony_ci case 1 ... 3: 2028c2ecf20Sopenharmony_ci case 7 ... 9: 2038c2ecf20Sopenharmony_ci if (reg & WM831X_GPN_PWR_DOM) 2048c2ecf20Sopenharmony_ci powerdomain = "VPMIC"; 2058c2ecf20Sopenharmony_ci else 2068c2ecf20Sopenharmony_ci powerdomain = "DBVDD"; 2078c2ecf20Sopenharmony_ci break; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_ci case 4 ... 6: 2108c2ecf20Sopenharmony_ci case 10 ... 12: 2118c2ecf20Sopenharmony_ci if (reg & WM831X_GPN_PWR_DOM) 2128c2ecf20Sopenharmony_ci powerdomain = "SYSVDD"; 2138c2ecf20Sopenharmony_ci else 2148c2ecf20Sopenharmony_ci powerdomain = "DBVDD"; 2158c2ecf20Sopenharmony_ci break; 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci case 13 ... 16: 2188c2ecf20Sopenharmony_ci powerdomain = "TPVDD"; 2198c2ecf20Sopenharmony_ci break; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci default: 2228c2ecf20Sopenharmony_ci BUG(); 2238c2ecf20Sopenharmony_ci break; 2248c2ecf20Sopenharmony_ci } 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci tristated = reg & WM831X_GPN_TRI; 2278c2ecf20Sopenharmony_ci if (wm831x->has_gpio_ena) 2288c2ecf20Sopenharmony_ci tristated = !tristated; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci seq_printf(s, " %s %s %s %s%s\n" 2318c2ecf20Sopenharmony_ci " %s%s (0x%4x)\n", 2328c2ecf20Sopenharmony_ci reg & WM831X_GPN_DIR ? "in" : "out", 2338c2ecf20Sopenharmony_ci wm831x_gpio_get(chip, i) ? "high" : "low", 2348c2ecf20Sopenharmony_ci pull, 2358c2ecf20Sopenharmony_ci powerdomain, 2368c2ecf20Sopenharmony_ci reg & WM831X_GPN_POL ? "" : " inverted", 2378c2ecf20Sopenharmony_ci reg & WM831X_GPN_OD ? "open-drain" : "push-pull", 2388c2ecf20Sopenharmony_ci tristated ? " tristated" : "", 2398c2ecf20Sopenharmony_ci reg); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci#else 2438c2ecf20Sopenharmony_ci#define wm831x_gpio_dbg_show NULL 2448c2ecf20Sopenharmony_ci#endif 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_cistatic const struct gpio_chip template_chip = { 2478c2ecf20Sopenharmony_ci .label = "wm831x", 2488c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2498c2ecf20Sopenharmony_ci .direction_input = wm831x_gpio_direction_in, 2508c2ecf20Sopenharmony_ci .get = wm831x_gpio_get, 2518c2ecf20Sopenharmony_ci .direction_output = wm831x_gpio_direction_out, 2528c2ecf20Sopenharmony_ci .set = wm831x_gpio_set, 2538c2ecf20Sopenharmony_ci .to_irq = wm831x_gpio_to_irq, 2548c2ecf20Sopenharmony_ci .set_config = wm831x_set_config, 2558c2ecf20Sopenharmony_ci .dbg_show = wm831x_gpio_dbg_show, 2568c2ecf20Sopenharmony_ci .can_sleep = true, 2578c2ecf20Sopenharmony_ci}; 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_cistatic int wm831x_gpio_probe(struct platform_device *pdev) 2608c2ecf20Sopenharmony_ci{ 2618c2ecf20Sopenharmony_ci struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); 2628c2ecf20Sopenharmony_ci struct wm831x_pdata *pdata = &wm831x->pdata; 2638c2ecf20Sopenharmony_ci struct wm831x_gpio *wm831x_gpio; 2648c2ecf20Sopenharmony_ci int ret; 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci wm831x_gpio = devm_kzalloc(&pdev->dev, sizeof(*wm831x_gpio), 2678c2ecf20Sopenharmony_ci GFP_KERNEL); 2688c2ecf20Sopenharmony_ci if (wm831x_gpio == NULL) 2698c2ecf20Sopenharmony_ci return -ENOMEM; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci wm831x_gpio->wm831x = wm831x; 2728c2ecf20Sopenharmony_ci wm831x_gpio->gpio_chip = template_chip; 2738c2ecf20Sopenharmony_ci wm831x_gpio->gpio_chip.ngpio = wm831x->num_gpio; 2748c2ecf20Sopenharmony_ci wm831x_gpio->gpio_chip.parent = &pdev->dev; 2758c2ecf20Sopenharmony_ci if (pdata && pdata->gpio_base) 2768c2ecf20Sopenharmony_ci wm831x_gpio->gpio_chip.base = pdata->gpio_base; 2778c2ecf20Sopenharmony_ci else 2788c2ecf20Sopenharmony_ci wm831x_gpio->gpio_chip.base = -1; 2798c2ecf20Sopenharmony_ci#ifdef CONFIG_OF_GPIO 2808c2ecf20Sopenharmony_ci wm831x_gpio->gpio_chip.of_node = wm831x->dev->of_node; 2818c2ecf20Sopenharmony_ci#endif 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci ret = devm_gpiochip_add_data(&pdev->dev, &wm831x_gpio->gpio_chip, 2848c2ecf20Sopenharmony_ci wm831x_gpio); 2858c2ecf20Sopenharmony_ci if (ret < 0) { 2868c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not register gpiochip, %d\n", ret); 2878c2ecf20Sopenharmony_ci return ret; 2888c2ecf20Sopenharmony_ci } 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, wm831x_gpio); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci return ret; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_cistatic struct platform_driver wm831x_gpio_driver = { 2968c2ecf20Sopenharmony_ci .driver.name = "wm831x-gpio", 2978c2ecf20Sopenharmony_ci .probe = wm831x_gpio_probe, 2988c2ecf20Sopenharmony_ci}; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cistatic int __init wm831x_gpio_init(void) 3018c2ecf20Sopenharmony_ci{ 3028c2ecf20Sopenharmony_ci return platform_driver_register(&wm831x_gpio_driver); 3038c2ecf20Sopenharmony_ci} 3048c2ecf20Sopenharmony_cisubsys_initcall(wm831x_gpio_init); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_cistatic void __exit wm831x_gpio_exit(void) 3078c2ecf20Sopenharmony_ci{ 3088c2ecf20Sopenharmony_ci platform_driver_unregister(&wm831x_gpio_driver); 3098c2ecf20Sopenharmony_ci} 3108c2ecf20Sopenharmony_cimodule_exit(wm831x_gpio_exit); 3118c2ecf20Sopenharmony_ci 3128c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 3138c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO interface for WM831x PMICs"); 3148c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3158c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:wm831x-gpio"); 316