18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * GPIO support for Cirrus Logic Madera codecs
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2015-2018 Cirrus Logic
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#include <linux/mfd/madera/core.h>
148c2ecf20Sopenharmony_ci#include <linux/mfd/madera/pdata.h>
158c2ecf20Sopenharmony_ci#include <linux/mfd/madera/registers.h>
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_cistruct madera_gpio {
188c2ecf20Sopenharmony_ci	struct madera *madera;
198c2ecf20Sopenharmony_ci	/* storage space for the gpio_chip we're using */
208c2ecf20Sopenharmony_ci	struct gpio_chip gpio_chip;
218c2ecf20Sopenharmony_ci};
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic int madera_gpio_get_direction(struct gpio_chip *chip,
248c2ecf20Sopenharmony_ci				     unsigned int offset)
258c2ecf20Sopenharmony_ci{
268c2ecf20Sopenharmony_ci	struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
278c2ecf20Sopenharmony_ci	struct madera *madera = madera_gpio->madera;
288c2ecf20Sopenharmony_ci	unsigned int reg_offset = 2 * offset;
298c2ecf20Sopenharmony_ci	unsigned int val;
308c2ecf20Sopenharmony_ci	int ret;
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	ret = regmap_read(madera->regmap, MADERA_GPIO1_CTRL_2 + reg_offset,
338c2ecf20Sopenharmony_ci			  &val);
348c2ecf20Sopenharmony_ci	if (ret < 0)
358c2ecf20Sopenharmony_ci		return ret;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci	if (val & MADERA_GP1_DIR_MASK)
388c2ecf20Sopenharmony_ci		return GPIO_LINE_DIRECTION_IN;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	return GPIO_LINE_DIRECTION_OUT;
418c2ecf20Sopenharmony_ci}
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic int madera_gpio_direction_in(struct gpio_chip *chip, unsigned int offset)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
468c2ecf20Sopenharmony_ci	struct madera *madera = madera_gpio->madera;
478c2ecf20Sopenharmony_ci	unsigned int reg_offset = 2 * offset;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	return regmap_update_bits(madera->regmap,
508c2ecf20Sopenharmony_ci				  MADERA_GPIO1_CTRL_2 + reg_offset,
518c2ecf20Sopenharmony_ci				  MADERA_GP1_DIR_MASK, MADERA_GP1_DIR);
528c2ecf20Sopenharmony_ci}
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_cistatic int madera_gpio_get(struct gpio_chip *chip, unsigned int offset)
558c2ecf20Sopenharmony_ci{
568c2ecf20Sopenharmony_ci	struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
578c2ecf20Sopenharmony_ci	struct madera *madera = madera_gpio->madera;
588c2ecf20Sopenharmony_ci	unsigned int reg_offset = 2 * offset;
598c2ecf20Sopenharmony_ci	unsigned int val;
608c2ecf20Sopenharmony_ci	int ret;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	ret = regmap_read(madera->regmap, MADERA_GPIO1_CTRL_1 + reg_offset,
638c2ecf20Sopenharmony_ci			  &val);
648c2ecf20Sopenharmony_ci	if (ret < 0)
658c2ecf20Sopenharmony_ci		return ret;
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci	return !!(val & MADERA_GP1_LVL_MASK);
688c2ecf20Sopenharmony_ci}
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_cistatic int madera_gpio_direction_out(struct gpio_chip *chip,
718c2ecf20Sopenharmony_ci				     unsigned int offset, int value)
728c2ecf20Sopenharmony_ci{
738c2ecf20Sopenharmony_ci	struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
748c2ecf20Sopenharmony_ci	struct madera *madera = madera_gpio->madera;
758c2ecf20Sopenharmony_ci	unsigned int reg_offset = 2 * offset;
768c2ecf20Sopenharmony_ci	unsigned int reg_val = value ? MADERA_GP1_LVL : 0;
778c2ecf20Sopenharmony_ci	int ret;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	ret = regmap_update_bits(madera->regmap,
808c2ecf20Sopenharmony_ci				 MADERA_GPIO1_CTRL_2 + reg_offset,
818c2ecf20Sopenharmony_ci				 MADERA_GP1_DIR_MASK, 0);
828c2ecf20Sopenharmony_ci	if (ret < 0)
838c2ecf20Sopenharmony_ci		return ret;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	return regmap_update_bits(madera->regmap,
868c2ecf20Sopenharmony_ci				  MADERA_GPIO1_CTRL_1 + reg_offset,
878c2ecf20Sopenharmony_ci				  MADERA_GP1_LVL_MASK, reg_val);
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic void madera_gpio_set(struct gpio_chip *chip, unsigned int offset,
918c2ecf20Sopenharmony_ci			    int value)
928c2ecf20Sopenharmony_ci{
938c2ecf20Sopenharmony_ci	struct madera_gpio *madera_gpio = gpiochip_get_data(chip);
948c2ecf20Sopenharmony_ci	struct madera *madera = madera_gpio->madera;
958c2ecf20Sopenharmony_ci	unsigned int reg_offset = 2 * offset;
968c2ecf20Sopenharmony_ci	unsigned int reg_val = value ? MADERA_GP1_LVL : 0;
978c2ecf20Sopenharmony_ci	int ret;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	ret = regmap_update_bits(madera->regmap,
1008c2ecf20Sopenharmony_ci				 MADERA_GPIO1_CTRL_1 + reg_offset,
1018c2ecf20Sopenharmony_ci				 MADERA_GP1_LVL_MASK, reg_val);
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	/* set() doesn't return an error so log a warning */
1048c2ecf20Sopenharmony_ci	if (ret)
1058c2ecf20Sopenharmony_ci		dev_warn(madera->dev, "Failed to write to 0x%x (%d)\n",
1068c2ecf20Sopenharmony_ci			 MADERA_GPIO1_CTRL_1 + reg_offset, ret);
1078c2ecf20Sopenharmony_ci}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_cistatic const struct gpio_chip madera_gpio_chip = {
1108c2ecf20Sopenharmony_ci	.label			= "madera",
1118c2ecf20Sopenharmony_ci	.owner			= THIS_MODULE,
1128c2ecf20Sopenharmony_ci	.request		= gpiochip_generic_request,
1138c2ecf20Sopenharmony_ci	.free			= gpiochip_generic_free,
1148c2ecf20Sopenharmony_ci	.get_direction		= madera_gpio_get_direction,
1158c2ecf20Sopenharmony_ci	.direction_input	= madera_gpio_direction_in,
1168c2ecf20Sopenharmony_ci	.get			= madera_gpio_get,
1178c2ecf20Sopenharmony_ci	.direction_output	= madera_gpio_direction_out,
1188c2ecf20Sopenharmony_ci	.set			= madera_gpio_set,
1198c2ecf20Sopenharmony_ci	.set_config		= gpiochip_generic_config,
1208c2ecf20Sopenharmony_ci	.can_sleep		= true,
1218c2ecf20Sopenharmony_ci};
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_cistatic int madera_gpio_probe(struct platform_device *pdev)
1248c2ecf20Sopenharmony_ci{
1258c2ecf20Sopenharmony_ci	struct madera *madera = dev_get_drvdata(pdev->dev.parent);
1268c2ecf20Sopenharmony_ci	struct madera_pdata *pdata = &madera->pdata;
1278c2ecf20Sopenharmony_ci	struct madera_gpio *madera_gpio;
1288c2ecf20Sopenharmony_ci	int ret;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	madera_gpio = devm_kzalloc(&pdev->dev, sizeof(*madera_gpio),
1318c2ecf20Sopenharmony_ci				   GFP_KERNEL);
1328c2ecf20Sopenharmony_ci	if (!madera_gpio)
1338c2ecf20Sopenharmony_ci		return -ENOMEM;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	madera_gpio->madera = madera;
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_ci	/* Construct suitable gpio_chip from the template in madera_gpio_chip */
1388c2ecf20Sopenharmony_ci	madera_gpio->gpio_chip = madera_gpio_chip;
1398c2ecf20Sopenharmony_ci	madera_gpio->gpio_chip.parent = pdev->dev.parent;
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	switch (madera->type) {
1428c2ecf20Sopenharmony_ci	case CS47L15:
1438c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.ngpio = CS47L15_NUM_GPIOS;
1448c2ecf20Sopenharmony_ci		break;
1458c2ecf20Sopenharmony_ci	case CS47L35:
1468c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.ngpio = CS47L35_NUM_GPIOS;
1478c2ecf20Sopenharmony_ci		break;
1488c2ecf20Sopenharmony_ci	case CS47L85:
1498c2ecf20Sopenharmony_ci	case WM1840:
1508c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.ngpio = CS47L85_NUM_GPIOS;
1518c2ecf20Sopenharmony_ci		break;
1528c2ecf20Sopenharmony_ci	case CS47L90:
1538c2ecf20Sopenharmony_ci	case CS47L91:
1548c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.ngpio = CS47L90_NUM_GPIOS;
1558c2ecf20Sopenharmony_ci		break;
1568c2ecf20Sopenharmony_ci	case CS42L92:
1578c2ecf20Sopenharmony_ci	case CS47L92:
1588c2ecf20Sopenharmony_ci	case CS47L93:
1598c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.ngpio = CS47L92_NUM_GPIOS;
1608c2ecf20Sopenharmony_ci		break;
1618c2ecf20Sopenharmony_ci	default:
1628c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Unknown chip variant %d\n", madera->type);
1638c2ecf20Sopenharmony_ci		return -EINVAL;
1648c2ecf20Sopenharmony_ci	}
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	/* We want to be usable on systems that don't use devicetree or acpi */
1678c2ecf20Sopenharmony_ci	if (pdata->gpio_base)
1688c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.base = pdata->gpio_base;
1698c2ecf20Sopenharmony_ci	else
1708c2ecf20Sopenharmony_ci		madera_gpio->gpio_chip.base = -1;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	ret = devm_gpiochip_add_data(&pdev->dev,
1738c2ecf20Sopenharmony_ci				     &madera_gpio->gpio_chip,
1748c2ecf20Sopenharmony_ci				     madera_gpio);
1758c2ecf20Sopenharmony_ci	if (ret < 0) {
1768c2ecf20Sopenharmony_ci		dev_dbg(&pdev->dev, "Could not register gpiochip, %d\n", ret);
1778c2ecf20Sopenharmony_ci		return ret;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	/*
1818c2ecf20Sopenharmony_ci	 * This is part of a composite MFD device which can only be used with
1828c2ecf20Sopenharmony_ci	 * the corresponding pinctrl driver. On all supported silicon the GPIO
1838c2ecf20Sopenharmony_ci	 * to pinctrl mapping is fixed in the silicon, so we register it
1848c2ecf20Sopenharmony_ci	 * explicitly instead of requiring a redundant gpio-ranges in the
1858c2ecf20Sopenharmony_ci	 * devicetree.
1868c2ecf20Sopenharmony_ci	 * In any case we also want to work on systems that don't use devicetree
1878c2ecf20Sopenharmony_ci	 * or acpi.
1888c2ecf20Sopenharmony_ci	 */
1898c2ecf20Sopenharmony_ci	ret = gpiochip_add_pin_range(&madera_gpio->gpio_chip, "madera-pinctrl",
1908c2ecf20Sopenharmony_ci				     0, 0, madera_gpio->gpio_chip.ngpio);
1918c2ecf20Sopenharmony_ci	if (ret) {
1928c2ecf20Sopenharmony_ci		dev_dbg(&pdev->dev, "Failed to add pin range (%d)\n", ret);
1938c2ecf20Sopenharmony_ci		return ret;
1948c2ecf20Sopenharmony_ci	}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_ci	return 0;
1978c2ecf20Sopenharmony_ci}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_cistatic struct platform_driver madera_gpio_driver = {
2008c2ecf20Sopenharmony_ci	.driver = {
2018c2ecf20Sopenharmony_ci		.name	= "madera-gpio",
2028c2ecf20Sopenharmony_ci	},
2038c2ecf20Sopenharmony_ci	.probe		= madera_gpio_probe,
2048c2ecf20Sopenharmony_ci};
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_cimodule_platform_driver(madera_gpio_driver);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ciMODULE_SOFTDEP("pre: pinctrl-madera");
2098c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("GPIO interface for Madera codecs");
2108c2ecf20Sopenharmony_ciMODULE_AUTHOR("Nariman Poushin <nariman@opensource.cirrus.com>");
2118c2ecf20Sopenharmony_ciMODULE_AUTHOR("Richard Fitzgerald <rf@opensource.cirrus.com>");
2128c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
2138c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:madera-gpio");
214