18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * RDC321x GPIO driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2008, Volker Weiss <dev@tintuc.de>
68c2ecf20Sopenharmony_ci * Copyright (C) 2007-2010 Florian Fainelli <florian@openwrt.org>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/kernel.h>
108c2ecf20Sopenharmony_ci#include <linux/init.h>
118c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
128c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
138c2ecf20Sopenharmony_ci#include <linux/pci.h>
148c2ecf20Sopenharmony_ci#include <linux/gpio/driver.h>
158c2ecf20Sopenharmony_ci#include <linux/mfd/rdc321x.h>
168c2ecf20Sopenharmony_ci#include <linux/slab.h>
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistruct rdc321x_gpio {
198c2ecf20Sopenharmony_ci	spinlock_t		lock;
208c2ecf20Sopenharmony_ci	struct pci_dev		*sb_pdev;
218c2ecf20Sopenharmony_ci	u32			data_reg[2];
228c2ecf20Sopenharmony_ci	int			reg1_ctrl_base;
238c2ecf20Sopenharmony_ci	int			reg1_data_base;
248c2ecf20Sopenharmony_ci	int			reg2_ctrl_base;
258c2ecf20Sopenharmony_ci	int			reg2_data_base;
268c2ecf20Sopenharmony_ci	struct gpio_chip	chip;
278c2ecf20Sopenharmony_ci};
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci/* read GPIO pin */
308c2ecf20Sopenharmony_cistatic int rdc_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
318c2ecf20Sopenharmony_ci{
328c2ecf20Sopenharmony_ci	struct rdc321x_gpio *gpch;
338c2ecf20Sopenharmony_ci	u32 value = 0;
348c2ecf20Sopenharmony_ci	int reg;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	gpch = gpiochip_get_data(chip);
378c2ecf20Sopenharmony_ci	reg = gpio < 32 ? gpch->reg1_data_base : gpch->reg2_data_base;
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	spin_lock(&gpch->lock);
408c2ecf20Sopenharmony_ci	pci_write_config_dword(gpch->sb_pdev, reg,
418c2ecf20Sopenharmony_ci					gpch->data_reg[gpio < 32 ? 0 : 1]);
428c2ecf20Sopenharmony_ci	pci_read_config_dword(gpch->sb_pdev, reg, &value);
438c2ecf20Sopenharmony_ci	spin_unlock(&gpch->lock);
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci	return (1 << (gpio & 0x1f)) & value ? 1 : 0;
468c2ecf20Sopenharmony_ci}
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_cistatic void rdc_gpio_set_value_impl(struct gpio_chip *chip,
498c2ecf20Sopenharmony_ci				unsigned gpio, int value)
508c2ecf20Sopenharmony_ci{
518c2ecf20Sopenharmony_ci	struct rdc321x_gpio *gpch;
528c2ecf20Sopenharmony_ci	int reg = (gpio < 32) ? 0 : 1;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	gpch = gpiochip_get_data(chip);
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (value)
578c2ecf20Sopenharmony_ci		gpch->data_reg[reg] |= 1 << (gpio & 0x1f);
588c2ecf20Sopenharmony_ci	else
598c2ecf20Sopenharmony_ci		gpch->data_reg[reg] &= ~(1 << (gpio & 0x1f));
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci	pci_write_config_dword(gpch->sb_pdev,
628c2ecf20Sopenharmony_ci			reg ? gpch->reg2_data_base : gpch->reg1_data_base,
638c2ecf20Sopenharmony_ci			gpch->data_reg[reg]);
648c2ecf20Sopenharmony_ci}
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci/* set GPIO pin to value */
678c2ecf20Sopenharmony_cistatic void rdc_gpio_set_value(struct gpio_chip *chip,
688c2ecf20Sopenharmony_ci				unsigned gpio, int value)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	struct rdc321x_gpio *gpch;
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci	gpch = gpiochip_get_data(chip);
738c2ecf20Sopenharmony_ci	spin_lock(&gpch->lock);
748c2ecf20Sopenharmony_ci	rdc_gpio_set_value_impl(chip, gpio, value);
758c2ecf20Sopenharmony_ci	spin_unlock(&gpch->lock);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic int rdc_gpio_config(struct gpio_chip *chip,
798c2ecf20Sopenharmony_ci				unsigned gpio, int value)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	struct rdc321x_gpio *gpch;
828c2ecf20Sopenharmony_ci	int err;
838c2ecf20Sopenharmony_ci	u32 reg;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	gpch = gpiochip_get_data(chip);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	spin_lock(&gpch->lock);
888c2ecf20Sopenharmony_ci	err = pci_read_config_dword(gpch->sb_pdev, gpio < 32 ?
898c2ecf20Sopenharmony_ci			gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, &reg);
908c2ecf20Sopenharmony_ci	if (err)
918c2ecf20Sopenharmony_ci		goto unlock;
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ci	reg |= 1 << (gpio & 0x1f);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	err = pci_write_config_dword(gpch->sb_pdev, gpio < 32 ?
968c2ecf20Sopenharmony_ci			gpch->reg1_ctrl_base : gpch->reg2_ctrl_base, reg);
978c2ecf20Sopenharmony_ci	if (err)
988c2ecf20Sopenharmony_ci		goto unlock;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	rdc_gpio_set_value_impl(chip, gpio, value);
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ciunlock:
1038c2ecf20Sopenharmony_ci	spin_unlock(&gpch->lock);
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_ci	return err;
1068c2ecf20Sopenharmony_ci}
1078c2ecf20Sopenharmony_ci
1088c2ecf20Sopenharmony_ci/* configure GPIO pin as input */
1098c2ecf20Sopenharmony_cistatic int rdc_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	return rdc_gpio_config(chip, gpio, 1);
1128c2ecf20Sopenharmony_ci}
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci/*
1158c2ecf20Sopenharmony_ci * Cache the initial value of both GPIO data registers
1168c2ecf20Sopenharmony_ci */
1178c2ecf20Sopenharmony_cistatic int rdc321x_gpio_probe(struct platform_device *pdev)
1188c2ecf20Sopenharmony_ci{
1198c2ecf20Sopenharmony_ci	int err;
1208c2ecf20Sopenharmony_ci	struct resource *r;
1218c2ecf20Sopenharmony_ci	struct rdc321x_gpio *rdc321x_gpio_dev;
1228c2ecf20Sopenharmony_ci	struct rdc321x_gpio_pdata *pdata;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	pdata = dev_get_platdata(&pdev->dev);
1258c2ecf20Sopenharmony_ci	if (!pdata) {
1268c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "no platform data supplied\n");
1278c2ecf20Sopenharmony_ci		return -ENODEV;
1288c2ecf20Sopenharmony_ci	}
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	rdc321x_gpio_dev = devm_kzalloc(&pdev->dev, sizeof(struct rdc321x_gpio),
1318c2ecf20Sopenharmony_ci					GFP_KERNEL);
1328c2ecf20Sopenharmony_ci	if (!rdc321x_gpio_dev)
1338c2ecf20Sopenharmony_ci		return -ENOMEM;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg1");
1368c2ecf20Sopenharmony_ci	if (!r) {
1378c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to get gpio-reg1 resource\n");
1388c2ecf20Sopenharmony_ci		return -ENODEV;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci
1418c2ecf20Sopenharmony_ci	spin_lock_init(&rdc321x_gpio_dev->lock);
1428c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->sb_pdev = pdata->sb_pdev;
1438c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->reg1_ctrl_base = r->start;
1448c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->reg1_data_base = r->start + 0x4;
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	r = platform_get_resource_byname(pdev, IORESOURCE_IO, "gpio-reg2");
1478c2ecf20Sopenharmony_ci	if (!r) {
1488c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "failed to get gpio-reg2 resource\n");
1498c2ecf20Sopenharmony_ci		return -ENODEV;
1508c2ecf20Sopenharmony_ci	}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->reg2_ctrl_base = r->start;
1538c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->reg2_data_base = r->start + 0x4;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.label = "rdc321x-gpio";
1568c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.owner = THIS_MODULE;
1578c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.direction_input = rdc_gpio_direction_input;
1588c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.direction_output = rdc_gpio_config;
1598c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.get = rdc_gpio_get_value;
1608c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.set = rdc_gpio_set_value;
1618c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.base = 0;
1628c2ecf20Sopenharmony_ci	rdc321x_gpio_dev->chip.ngpio = pdata->max_gpios;
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	platform_set_drvdata(pdev, rdc321x_gpio_dev);
1658c2ecf20Sopenharmony_ci
1668c2ecf20Sopenharmony_ci	/* This might not be, what others (BIOS, bootloader, etc.)
1678c2ecf20Sopenharmony_ci	   wrote to these registers before, but it's a good guess. Still
1688c2ecf20Sopenharmony_ci	   better than just using 0xffffffff. */
1698c2ecf20Sopenharmony_ci	err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev,
1708c2ecf20Sopenharmony_ci					rdc321x_gpio_dev->reg1_data_base,
1718c2ecf20Sopenharmony_ci					&rdc321x_gpio_dev->data_reg[0]);
1728c2ecf20Sopenharmony_ci	if (err)
1738c2ecf20Sopenharmony_ci		return err;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	err = pci_read_config_dword(rdc321x_gpio_dev->sb_pdev,
1768c2ecf20Sopenharmony_ci					rdc321x_gpio_dev->reg2_data_base,
1778c2ecf20Sopenharmony_ci					&rdc321x_gpio_dev->data_reg[1]);
1788c2ecf20Sopenharmony_ci	if (err)
1798c2ecf20Sopenharmony_ci		return err;
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "registering %d GPIOs\n",
1828c2ecf20Sopenharmony_ci					rdc321x_gpio_dev->chip.ngpio);
1838c2ecf20Sopenharmony_ci	return devm_gpiochip_add_data(&pdev->dev, &rdc321x_gpio_dev->chip,
1848c2ecf20Sopenharmony_ci				      rdc321x_gpio_dev);
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_cistatic struct platform_driver rdc321x_gpio_driver = {
1888c2ecf20Sopenharmony_ci	.driver.name	= "rdc321x-gpio",
1898c2ecf20Sopenharmony_ci	.probe		= rdc321x_gpio_probe,
1908c2ecf20Sopenharmony_ci};
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_cimodule_platform_driver(rdc321x_gpio_driver);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ciMODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
1958c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("RDC321x GPIO driver");
1968c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
1978c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:rdc321x-gpio");
198