18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * gpiolib support for Wolfson Arizona class devices 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright 2012 Wolfson Microelectronics PLC. 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> 88c2ecf20Sopenharmony_ci */ 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/slab.h> 128c2ecf20Sopenharmony_ci#include <linux/module.h> 138c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h> 148c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 158c2ecf20Sopenharmony_ci#include <linux/pm_runtime.h> 168c2ecf20Sopenharmony_ci#include <linux/seq_file.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_ci#include <linux/mfd/arizona/core.h> 198c2ecf20Sopenharmony_ci#include <linux/mfd/arizona/pdata.h> 208c2ecf20Sopenharmony_ci#include <linux/mfd/arizona/registers.h> 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistruct arizona_gpio { 238c2ecf20Sopenharmony_ci struct arizona *arizona; 248c2ecf20Sopenharmony_ci struct gpio_chip gpio_chip; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic int arizona_gpio_direction_in(struct gpio_chip *chip, unsigned offset) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip); 308c2ecf20Sopenharmony_ci struct arizona *arizona = arizona_gpio->arizona; 318c2ecf20Sopenharmony_ci bool persistent = gpiochip_line_is_persistent(chip, offset); 328c2ecf20Sopenharmony_ci bool change; 338c2ecf20Sopenharmony_ci int ret; 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_ci ret = regmap_update_bits_check(arizona->regmap, 368c2ecf20Sopenharmony_ci ARIZONA_GPIO1_CTRL + offset, 378c2ecf20Sopenharmony_ci ARIZONA_GPN_DIR, ARIZONA_GPN_DIR, 388c2ecf20Sopenharmony_ci &change); 398c2ecf20Sopenharmony_ci if (ret < 0) 408c2ecf20Sopenharmony_ci return ret; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci if (change && persistent) { 438c2ecf20Sopenharmony_ci pm_runtime_mark_last_busy(chip->parent); 448c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(chip->parent); 458c2ecf20Sopenharmony_ci } 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci return 0; 488c2ecf20Sopenharmony_ci} 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_cistatic int arizona_gpio_get(struct gpio_chip *chip, unsigned offset) 518c2ecf20Sopenharmony_ci{ 528c2ecf20Sopenharmony_ci struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip); 538c2ecf20Sopenharmony_ci struct arizona *arizona = arizona_gpio->arizona; 548c2ecf20Sopenharmony_ci unsigned int reg, val; 558c2ecf20Sopenharmony_ci int ret; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci reg = ARIZONA_GPIO1_CTRL + offset; 588c2ecf20Sopenharmony_ci ret = regmap_read(arizona->regmap, reg, &val); 598c2ecf20Sopenharmony_ci if (ret < 0) 608c2ecf20Sopenharmony_ci return ret; 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci /* Resume to read actual registers for input pins */ 638c2ecf20Sopenharmony_ci if (val & ARIZONA_GPN_DIR) { 648c2ecf20Sopenharmony_ci ret = pm_runtime_get_sync(chip->parent); 658c2ecf20Sopenharmony_ci if (ret < 0) { 668c2ecf20Sopenharmony_ci dev_err(chip->parent, "Failed to resume: %d\n", ret); 678c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(chip->parent); 688c2ecf20Sopenharmony_ci return ret; 698c2ecf20Sopenharmony_ci } 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci /* Register is cached, drop it to ensure a physical read */ 728c2ecf20Sopenharmony_ci ret = regcache_drop_region(arizona->regmap, reg, reg); 738c2ecf20Sopenharmony_ci if (ret < 0) { 748c2ecf20Sopenharmony_ci dev_err(chip->parent, "Failed to drop cache: %d\n", 758c2ecf20Sopenharmony_ci ret); 768c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(chip->parent); 778c2ecf20Sopenharmony_ci return ret; 788c2ecf20Sopenharmony_ci } 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci ret = regmap_read(arizona->regmap, reg, &val); 818c2ecf20Sopenharmony_ci if (ret < 0) { 828c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(chip->parent); 838c2ecf20Sopenharmony_ci return ret; 848c2ecf20Sopenharmony_ci } 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci pm_runtime_mark_last_busy(chip->parent); 878c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(chip->parent); 888c2ecf20Sopenharmony_ci } 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (val & ARIZONA_GPN_LVL) 918c2ecf20Sopenharmony_ci return 1; 928c2ecf20Sopenharmony_ci else 938c2ecf20Sopenharmony_ci return 0; 948c2ecf20Sopenharmony_ci} 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_cistatic int arizona_gpio_direction_out(struct gpio_chip *chip, 978c2ecf20Sopenharmony_ci unsigned offset, int value) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip); 1008c2ecf20Sopenharmony_ci struct arizona *arizona = arizona_gpio->arizona; 1018c2ecf20Sopenharmony_ci bool persistent = gpiochip_line_is_persistent(chip, offset); 1028c2ecf20Sopenharmony_ci unsigned int val; 1038c2ecf20Sopenharmony_ci int ret; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci ret = regmap_read(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, &val); 1068c2ecf20Sopenharmony_ci if (ret < 0) 1078c2ecf20Sopenharmony_ci return ret; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ci if ((val & ARIZONA_GPN_DIR) && persistent) { 1108c2ecf20Sopenharmony_ci ret = pm_runtime_get_sync(chip->parent); 1118c2ecf20Sopenharmony_ci if (ret < 0) { 1128c2ecf20Sopenharmony_ci dev_err(chip->parent, "Failed to resume: %d\n", ret); 1138c2ecf20Sopenharmony_ci pm_runtime_put(chip->parent); 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci } 1168c2ecf20Sopenharmony_ci } 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci if (value) 1198c2ecf20Sopenharmony_ci value = ARIZONA_GPN_LVL; 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci return regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, 1228c2ecf20Sopenharmony_ci ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value); 1238c2ecf20Sopenharmony_ci} 1248c2ecf20Sopenharmony_ci 1258c2ecf20Sopenharmony_cistatic void arizona_gpio_set(struct gpio_chip *chip, unsigned offset, int value) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip); 1288c2ecf20Sopenharmony_ci struct arizona *arizona = arizona_gpio->arizona; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci if (value) 1318c2ecf20Sopenharmony_ci value = ARIZONA_GPN_LVL; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, 1348c2ecf20Sopenharmony_ci ARIZONA_GPN_LVL, value); 1358c2ecf20Sopenharmony_ci} 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_cistatic const struct gpio_chip template_chip = { 1388c2ecf20Sopenharmony_ci .label = "arizona", 1398c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 1408c2ecf20Sopenharmony_ci .direction_input = arizona_gpio_direction_in, 1418c2ecf20Sopenharmony_ci .get = arizona_gpio_get, 1428c2ecf20Sopenharmony_ci .direction_output = arizona_gpio_direction_out, 1438c2ecf20Sopenharmony_ci .set = arizona_gpio_set, 1448c2ecf20Sopenharmony_ci .can_sleep = true, 1458c2ecf20Sopenharmony_ci}; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic int arizona_gpio_probe(struct platform_device *pdev) 1488c2ecf20Sopenharmony_ci{ 1498c2ecf20Sopenharmony_ci struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); 1508c2ecf20Sopenharmony_ci struct arizona_pdata *pdata = &arizona->pdata; 1518c2ecf20Sopenharmony_ci struct arizona_gpio *arizona_gpio; 1528c2ecf20Sopenharmony_ci int ret; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci arizona_gpio = devm_kzalloc(&pdev->dev, sizeof(*arizona_gpio), 1558c2ecf20Sopenharmony_ci GFP_KERNEL); 1568c2ecf20Sopenharmony_ci if (!arizona_gpio) 1578c2ecf20Sopenharmony_ci return -ENOMEM; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci arizona_gpio->arizona = arizona; 1608c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip = template_chip; 1618c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip.parent = &pdev->dev; 1628c2ecf20Sopenharmony_ci#ifdef CONFIG_OF_GPIO 1638c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip.of_node = arizona->dev->of_node; 1648c2ecf20Sopenharmony_ci#endif 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci switch (arizona->type) { 1678c2ecf20Sopenharmony_ci case WM5102: 1688c2ecf20Sopenharmony_ci case WM5110: 1698c2ecf20Sopenharmony_ci case WM8280: 1708c2ecf20Sopenharmony_ci case WM8997: 1718c2ecf20Sopenharmony_ci case WM8998: 1728c2ecf20Sopenharmony_ci case WM1814: 1738c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip.ngpio = 5; 1748c2ecf20Sopenharmony_ci break; 1758c2ecf20Sopenharmony_ci case WM1831: 1768c2ecf20Sopenharmony_ci case CS47L24: 1778c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip.ngpio = 2; 1788c2ecf20Sopenharmony_ci break; 1798c2ecf20Sopenharmony_ci default: 1808c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unknown chip variant %d\n", 1818c2ecf20Sopenharmony_ci arizona->type); 1828c2ecf20Sopenharmony_ci return -EINVAL; 1838c2ecf20Sopenharmony_ci } 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci if (pdata->gpio_base) 1868c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip.base = pdata->gpio_base; 1878c2ecf20Sopenharmony_ci else 1888c2ecf20Sopenharmony_ci arizona_gpio->gpio_chip.base = -1; 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci pm_runtime_enable(&pdev->dev); 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci ret = devm_gpiochip_add_data(&pdev->dev, &arizona_gpio->gpio_chip, 1938c2ecf20Sopenharmony_ci arizona_gpio); 1948c2ecf20Sopenharmony_ci if (ret < 0) { 1958c2ecf20Sopenharmony_ci pm_runtime_disable(&pdev->dev); 1968c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Could not register gpiochip, %d\n", 1978c2ecf20Sopenharmony_ci ret); 1988c2ecf20Sopenharmony_ci return ret; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci return 0; 2028c2ecf20Sopenharmony_ci} 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_cistatic struct platform_driver arizona_gpio_driver = { 2058c2ecf20Sopenharmony_ci .driver.name = "arizona-gpio", 2068c2ecf20Sopenharmony_ci .probe = arizona_gpio_probe, 2078c2ecf20Sopenharmony_ci}; 2088c2ecf20Sopenharmony_ci 2098c2ecf20Sopenharmony_cimodule_platform_driver(arizona_gpio_driver); 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); 2128c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO interface for Arizona devices"); 2138c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 2148c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:arizona-gpio"); 215