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