213 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * gpiolib support for Wolfson Arizona class devices4 *5 * Copyright 2012 Wolfson Microelectronics PLC.6 *7 * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>8 */9 10#include <linux/gpio/driver.h>11#include <linux/kernel.h>12#include <linux/module.h>13#include <linux/platform_device.h>14#include <linux/pm_runtime.h>15#include <linux/slab.h>16 17#include <linux/mfd/arizona/core.h>18#include <linux/mfd/arizona/pdata.h>19#include <linux/mfd/arizona/registers.h>20 21struct arizona_gpio {22 struct arizona *arizona;23 struct gpio_chip gpio_chip;24};25 26static int arizona_gpio_direction_in(struct gpio_chip *chip, unsigned offset)27{28 struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);29 struct arizona *arizona = arizona_gpio->arizona;30 bool persistent = gpiochip_line_is_persistent(chip, offset);31 bool change;32 int ret;33 34 ret = regmap_update_bits_check(arizona->regmap,35 ARIZONA_GPIO1_CTRL + offset,36 ARIZONA_GPN_DIR, ARIZONA_GPN_DIR,37 &change);38 if (ret < 0)39 return ret;40 41 if (change && persistent) {42 pm_runtime_mark_last_busy(chip->parent);43 pm_runtime_put_autosuspend(chip->parent);44 }45 46 return 0;47}48 49static int arizona_gpio_get(struct gpio_chip *chip, unsigned offset)50{51 struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);52 struct arizona *arizona = arizona_gpio->arizona;53 unsigned int reg, val;54 int ret;55 56 reg = ARIZONA_GPIO1_CTRL + offset;57 ret = regmap_read(arizona->regmap, reg, &val);58 if (ret < 0)59 return ret;60 61 /* Resume to read actual registers for input pins */62 if (val & ARIZONA_GPN_DIR) {63 ret = pm_runtime_get_sync(chip->parent);64 if (ret < 0) {65 dev_err(chip->parent, "Failed to resume: %d\n", ret);66 pm_runtime_put_autosuspend(chip->parent);67 return ret;68 }69 70 /* Register is cached, drop it to ensure a physical read */71 ret = regcache_drop_region(arizona->regmap, reg, reg);72 if (ret < 0) {73 dev_err(chip->parent, "Failed to drop cache: %d\n",74 ret);75 pm_runtime_put_autosuspend(chip->parent);76 return ret;77 }78 79 ret = regmap_read(arizona->regmap, reg, &val);80 if (ret < 0) {81 pm_runtime_put_autosuspend(chip->parent);82 return ret;83 }84 85 pm_runtime_mark_last_busy(chip->parent);86 pm_runtime_put_autosuspend(chip->parent);87 }88 89 if (val & ARIZONA_GPN_LVL)90 return 1;91 else92 return 0;93}94 95static int arizona_gpio_direction_out(struct gpio_chip *chip,96 unsigned offset, int value)97{98 struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);99 struct arizona *arizona = arizona_gpio->arizona;100 bool persistent = gpiochip_line_is_persistent(chip, offset);101 unsigned int val;102 int ret;103 104 ret = regmap_read(arizona->regmap, ARIZONA_GPIO1_CTRL + offset, &val);105 if (ret < 0)106 return ret;107 108 if ((val & ARIZONA_GPN_DIR) && persistent) {109 ret = pm_runtime_get_sync(chip->parent);110 if (ret < 0) {111 dev_err(chip->parent, "Failed to resume: %d\n", ret);112 pm_runtime_put(chip->parent);113 return ret;114 }115 }116 117 if (value)118 value = ARIZONA_GPN_LVL;119 120 return regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset,121 ARIZONA_GPN_DIR | ARIZONA_GPN_LVL, value);122}123 124static void arizona_gpio_set(struct gpio_chip *chip, unsigned offset, int value)125{126 struct arizona_gpio *arizona_gpio = gpiochip_get_data(chip);127 struct arizona *arizona = arizona_gpio->arizona;128 129 if (value)130 value = ARIZONA_GPN_LVL;131 132 regmap_update_bits(arizona->regmap, ARIZONA_GPIO1_CTRL + offset,133 ARIZONA_GPN_LVL, value);134}135 136static const struct gpio_chip template_chip = {137 .label = "arizona",138 .owner = THIS_MODULE,139 .direction_input = arizona_gpio_direction_in,140 .get = arizona_gpio_get,141 .direction_output = arizona_gpio_direction_out,142 .set = arizona_gpio_set,143 .can_sleep = true,144};145 146static int arizona_gpio_probe(struct platform_device *pdev)147{148 struct arizona *arizona = dev_get_drvdata(pdev->dev.parent);149 struct arizona_pdata *pdata = &arizona->pdata;150 struct arizona_gpio *arizona_gpio;151 int ret;152 153 device_set_node(&pdev->dev, dev_fwnode(pdev->dev.parent));154 155 arizona_gpio = devm_kzalloc(&pdev->dev, sizeof(*arizona_gpio),156 GFP_KERNEL);157 if (!arizona_gpio)158 return -ENOMEM;159 160 arizona_gpio->arizona = arizona;161 arizona_gpio->gpio_chip = template_chip;162 arizona_gpio->gpio_chip.parent = &pdev->dev;163 164 switch (arizona->type) {165 case WM5102:166 case WM5110:167 case WM8280:168 case WM8997:169 case WM8998:170 case WM1814:171 arizona_gpio->gpio_chip.ngpio = 5;172 break;173 case WM1831:174 case CS47L24:175 arizona_gpio->gpio_chip.ngpio = 2;176 break;177 default:178 dev_err(&pdev->dev, "Unknown chip variant %d\n",179 arizona->type);180 return -EINVAL;181 }182 183 if (pdata->gpio_base)184 arizona_gpio->gpio_chip.base = pdata->gpio_base;185 else186 arizona_gpio->gpio_chip.base = -1;187 188 pm_runtime_enable(&pdev->dev);189 190 ret = devm_gpiochip_add_data(&pdev->dev, &arizona_gpio->gpio_chip,191 arizona_gpio);192 if (ret < 0) {193 pm_runtime_disable(&pdev->dev);194 dev_err(&pdev->dev, "Could not register gpiochip, %d\n",195 ret);196 return ret;197 }198 199 return 0;200}201 202static struct platform_driver arizona_gpio_driver = {203 .driver.name = "arizona-gpio",204 .probe = arizona_gpio_probe,205};206 207module_platform_driver(arizona_gpio_driver);208 209MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");210MODULE_DESCRIPTION("GPIO interface for Arizona devices");211MODULE_LICENSE("GPL");212MODULE_ALIAS("platform:arizona-gpio");213