162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (C) 2019 Bootlin
462306a36Sopenharmony_ci * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/err.h>
862306a36Sopenharmony_ci#include <linux/gpio/driver.h>
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/of.h>
1162306a36Sopenharmony_ci#include <linux/of_address.h>
1262306a36Sopenharmony_ci#include <linux/platform_device.h>
1362306a36Sopenharmony_ci#include <linux/regmap.h>
1462306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define LOGICVC_CTRL_REG		0x40
1762306a36Sopenharmony_ci#define LOGICVC_CTRL_GPIO_SHIFT		11
1862306a36Sopenharmony_ci#define LOGICVC_CTRL_GPIO_BITS		5
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define LOGICVC_POWER_CTRL_REG		0x78
2162306a36Sopenharmony_ci#define LOGICVC_POWER_CTRL_GPIO_SHIFT	0
2262306a36Sopenharmony_ci#define LOGICVC_POWER_CTRL_GPIO_BITS	4
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistruct logicvc_gpio {
2562306a36Sopenharmony_ci	struct gpio_chip chip;
2662306a36Sopenharmony_ci	struct regmap *regmap;
2762306a36Sopenharmony_ci};
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_cistatic void logicvc_gpio_offset(struct logicvc_gpio *logicvc, unsigned offset,
3062306a36Sopenharmony_ci				unsigned int *reg, unsigned int *bit)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	if (offset >= LOGICVC_CTRL_GPIO_BITS) {
3362306a36Sopenharmony_ci		*reg = LOGICVC_POWER_CTRL_REG;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci		/* To the (virtual) power ctrl offset. */
3662306a36Sopenharmony_ci		offset -= LOGICVC_CTRL_GPIO_BITS;
3762306a36Sopenharmony_ci		/* To the actual bit offset in reg. */
3862306a36Sopenharmony_ci		offset += LOGICVC_POWER_CTRL_GPIO_SHIFT;
3962306a36Sopenharmony_ci	} else {
4062306a36Sopenharmony_ci		*reg = LOGICVC_CTRL_REG;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci		/* To the actual bit offset in reg. */
4362306a36Sopenharmony_ci		offset += LOGICVC_CTRL_GPIO_SHIFT;
4462306a36Sopenharmony_ci	}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	*bit = BIT(offset);
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic int logicvc_gpio_get(struct gpio_chip *chip, unsigned offset)
5062306a36Sopenharmony_ci{
5162306a36Sopenharmony_ci	struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
5262306a36Sopenharmony_ci	unsigned int reg, bit, value;
5362306a36Sopenharmony_ci	int ret;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	logicvc_gpio_offset(logicvc, offset, &reg, &bit);
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	ret = regmap_read(logicvc->regmap, reg, &value);
5862306a36Sopenharmony_ci	if (ret)
5962306a36Sopenharmony_ci		return ret;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	return !!(value & bit);
6262306a36Sopenharmony_ci}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_cistatic void logicvc_gpio_set(struct gpio_chip *chip, unsigned offset, int value)
6562306a36Sopenharmony_ci{
6662306a36Sopenharmony_ci	struct logicvc_gpio *logicvc = gpiochip_get_data(chip);
6762306a36Sopenharmony_ci	unsigned int reg, bit;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	logicvc_gpio_offset(logicvc, offset, &reg, &bit);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	regmap_update_bits(logicvc->regmap, reg, bit, value ? bit : 0);
7262306a36Sopenharmony_ci}
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic int logicvc_gpio_direction_output(struct gpio_chip *chip,
7562306a36Sopenharmony_ci					 unsigned offset, int value)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	/* Pins are always configured as output, so just set the value. */
7862306a36Sopenharmony_ci	logicvc_gpio_set(chip, offset, value);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return 0;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic struct regmap_config logicvc_gpio_regmap_config = {
8462306a36Sopenharmony_ci	.reg_bits	= 32,
8562306a36Sopenharmony_ci	.val_bits	= 32,
8662306a36Sopenharmony_ci	.reg_stride	= 4,
8762306a36Sopenharmony_ci	.name		= "logicvc-gpio",
8862306a36Sopenharmony_ci};
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int logicvc_gpio_probe(struct platform_device *pdev)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
9362306a36Sopenharmony_ci	struct device_node *of_node = dev->of_node;
9462306a36Sopenharmony_ci	struct logicvc_gpio *logicvc;
9562306a36Sopenharmony_ci	int ret;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	logicvc = devm_kzalloc(dev, sizeof(*logicvc), GFP_KERNEL);
9862306a36Sopenharmony_ci	if (!logicvc)
9962306a36Sopenharmony_ci		return -ENOMEM;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/* Try to get regmap from parent first. */
10262306a36Sopenharmony_ci	logicvc->regmap = syscon_node_to_regmap(of_node->parent);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Grab our own regmap if that fails. */
10562306a36Sopenharmony_ci	if (IS_ERR(logicvc->regmap)) {
10662306a36Sopenharmony_ci		struct resource res;
10762306a36Sopenharmony_ci		void __iomem *base;
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci		ret = of_address_to_resource(of_node, 0, &res);
11062306a36Sopenharmony_ci		if (ret) {
11162306a36Sopenharmony_ci			dev_err(dev, "Failed to get resource from address\n");
11262306a36Sopenharmony_ci			return ret;
11362306a36Sopenharmony_ci		}
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci		base = devm_ioremap_resource(dev, &res);
11662306a36Sopenharmony_ci		if (IS_ERR(base))
11762306a36Sopenharmony_ci			return PTR_ERR(base);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci		logicvc_gpio_regmap_config.max_register = resource_size(&res) -
12062306a36Sopenharmony_ci			logicvc_gpio_regmap_config.reg_stride;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		logicvc->regmap =
12362306a36Sopenharmony_ci			devm_regmap_init_mmio(dev, base,
12462306a36Sopenharmony_ci					      &logicvc_gpio_regmap_config);
12562306a36Sopenharmony_ci		if (IS_ERR(logicvc->regmap)) {
12662306a36Sopenharmony_ci			dev_err(dev, "Failed to create regmap for I/O\n");
12762306a36Sopenharmony_ci			return PTR_ERR(logicvc->regmap);
12862306a36Sopenharmony_ci		}
12962306a36Sopenharmony_ci	}
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	logicvc->chip.parent = dev;
13262306a36Sopenharmony_ci	logicvc->chip.owner = THIS_MODULE;
13362306a36Sopenharmony_ci	logicvc->chip.label = dev_name(dev);
13462306a36Sopenharmony_ci	logicvc->chip.base = -1;
13562306a36Sopenharmony_ci	logicvc->chip.ngpio = LOGICVC_CTRL_GPIO_BITS +
13662306a36Sopenharmony_ci			      LOGICVC_POWER_CTRL_GPIO_BITS;
13762306a36Sopenharmony_ci	logicvc->chip.get = logicvc_gpio_get;
13862306a36Sopenharmony_ci	logicvc->chip.set = logicvc_gpio_set;
13962306a36Sopenharmony_ci	logicvc->chip.direction_output = logicvc_gpio_direction_output;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	return devm_gpiochip_add_data(dev, &logicvc->chip, logicvc);
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic const struct of_device_id logicivc_gpio_of_table[] = {
14562306a36Sopenharmony_ci	{
14662306a36Sopenharmony_ci		.compatible	= "xylon,logicvc-3.02.a-gpio",
14762306a36Sopenharmony_ci	},
14862306a36Sopenharmony_ci	{ }
14962306a36Sopenharmony_ci};
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, logicivc_gpio_of_table);
15262306a36Sopenharmony_ci
15362306a36Sopenharmony_cistatic struct platform_driver logicvc_gpio_driver = {
15462306a36Sopenharmony_ci	.driver	= {
15562306a36Sopenharmony_ci		.name		= "gpio-logicvc",
15662306a36Sopenharmony_ci		.of_match_table	= logicivc_gpio_of_table,
15762306a36Sopenharmony_ci	},
15862306a36Sopenharmony_ci	.probe	= logicvc_gpio_probe,
15962306a36Sopenharmony_ci};
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cimodule_platform_driver(logicvc_gpio_driver);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ciMODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.com>");
16462306a36Sopenharmony_ciMODULE_DESCRIPTION("Xylon LogiCVC GPIO driver");
16562306a36Sopenharmony_ciMODULE_LICENSE("GPL");
166