162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * SAMA5D2 PIOBU GPIO controller
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Author: Andrei Stefanescu <andrei.stefanescu@microchip.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci#include <linux/bits.h>
1162306a36Sopenharmony_ci#include <linux/gpio/driver.h>
1262306a36Sopenharmony_ci#include <linux/init.h>
1362306a36Sopenharmony_ci#include <linux/kernel.h>
1462306a36Sopenharmony_ci#include <linux/mfd/syscon.h>
1562306a36Sopenharmony_ci#include <linux/module.h>
1662306a36Sopenharmony_ci#include <linux/of.h>
1762306a36Sopenharmony_ci#include <linux/platform_device.h>
1862306a36Sopenharmony_ci#include <linux/regmap.h>
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci#define PIOBU_NUM 8
2162306a36Sopenharmony_ci#define PIOBU_REG_SIZE 4
2262306a36Sopenharmony_ci
2362306a36Sopenharmony_ci/*
2462306a36Sopenharmony_ci * backup mode protection register for tamper detection
2562306a36Sopenharmony_ci * normal mode protection register for tamper detection
2662306a36Sopenharmony_ci * wakeup signal generation
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_ci#define PIOBU_BMPR 0x7C
2962306a36Sopenharmony_ci#define PIOBU_NMPR 0x80
3062306a36Sopenharmony_ci#define PIOBU_WKPR 0x90
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci#define PIOBU_BASE 0x18 /* PIOBU offset from SECUMOD base register address. */
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci#define PIOBU_DET_OFFSET 16
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci/* In the datasheet this bit is called OUTPUT */
3762306a36Sopenharmony_ci#define PIOBU_DIRECTION BIT(8)
3862306a36Sopenharmony_ci#define PIOBU_OUT BIT(8)
3962306a36Sopenharmony_ci#define PIOBU_IN 0
4062306a36Sopenharmony_ci
4162306a36Sopenharmony_ci#define PIOBU_SOD BIT(9)
4262306a36Sopenharmony_ci#define PIOBU_PDS BIT(10)
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci#define PIOBU_HIGH BIT(9)
4562306a36Sopenharmony_ci#define PIOBU_LOW 0
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_cistruct sama5d2_piobu {
4862306a36Sopenharmony_ci	struct gpio_chip chip;
4962306a36Sopenharmony_ci	struct regmap *regmap;
5062306a36Sopenharmony_ci};
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci/*
5362306a36Sopenharmony_ci * sama5d2_piobu_setup_pin() - prepares a pin for set_direction call
5462306a36Sopenharmony_ci *
5562306a36Sopenharmony_ci * Do not consider pin for tamper detection (normal and backup modes)
5662306a36Sopenharmony_ci * Do not consider pin as tamper wakeup interrupt source
5762306a36Sopenharmony_ci */
5862306a36Sopenharmony_cistatic int sama5d2_piobu_setup_pin(struct gpio_chip *chip, unsigned int pin)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	int ret;
6162306a36Sopenharmony_ci	struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
6262306a36Sopenharmony_ci						   chip);
6362306a36Sopenharmony_ci	unsigned int mask = BIT(PIOBU_DET_OFFSET + pin);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	ret = regmap_update_bits(piobu->regmap, PIOBU_BMPR, mask, 0);
6662306a36Sopenharmony_ci	if (ret)
6762306a36Sopenharmony_ci		return ret;
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci	ret = regmap_update_bits(piobu->regmap, PIOBU_NMPR, mask, 0);
7062306a36Sopenharmony_ci	if (ret)
7162306a36Sopenharmony_ci		return ret;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	return regmap_update_bits(piobu->regmap, PIOBU_WKPR, mask, 0);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci/*
7762306a36Sopenharmony_ci * sama5d2_piobu_write_value() - writes value & mask at the pin's PIOBU register
7862306a36Sopenharmony_ci */
7962306a36Sopenharmony_cistatic int sama5d2_piobu_write_value(struct gpio_chip *chip, unsigned int pin,
8062306a36Sopenharmony_ci				     unsigned int mask, unsigned int value)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	int reg;
8362306a36Sopenharmony_ci	struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
8462306a36Sopenharmony_ci						   chip);
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	reg = PIOBU_BASE + pin * PIOBU_REG_SIZE;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	return regmap_update_bits(piobu->regmap, reg, mask, value);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/*
9262306a36Sopenharmony_ci * sama5d2_piobu_read_value() - read the value with masking from the pin's PIOBU
9362306a36Sopenharmony_ci *			      register
9462306a36Sopenharmony_ci */
9562306a36Sopenharmony_cistatic int sama5d2_piobu_read_value(struct gpio_chip *chip, unsigned int pin,
9662306a36Sopenharmony_ci				    unsigned int mask)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
9962306a36Sopenharmony_ci						   chip);
10062306a36Sopenharmony_ci	unsigned int val, reg;
10162306a36Sopenharmony_ci	int ret;
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	reg = PIOBU_BASE + pin * PIOBU_REG_SIZE;
10462306a36Sopenharmony_ci	ret = regmap_read(piobu->regmap, reg, &val);
10562306a36Sopenharmony_ci	if (ret < 0)
10662306a36Sopenharmony_ci		return ret;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	return val & mask;
10962306a36Sopenharmony_ci}
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci/*
11262306a36Sopenharmony_ci * sama5d2_piobu_get_direction() - gpiochip get_direction
11362306a36Sopenharmony_ci */
11462306a36Sopenharmony_cistatic int sama5d2_piobu_get_direction(struct gpio_chip *chip,
11562306a36Sopenharmony_ci				       unsigned int pin)
11662306a36Sopenharmony_ci{
11762306a36Sopenharmony_ci	int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	if (ret < 0)
12062306a36Sopenharmony_ci		return ret;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	return (ret == PIOBU_IN) ? GPIO_LINE_DIRECTION_IN :
12362306a36Sopenharmony_ci				   GPIO_LINE_DIRECTION_OUT;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/*
12762306a36Sopenharmony_ci * sama5d2_piobu_direction_input() - gpiochip direction_input
12862306a36Sopenharmony_ci */
12962306a36Sopenharmony_cistatic int sama5d2_piobu_direction_input(struct gpio_chip *chip,
13062306a36Sopenharmony_ci					 unsigned int pin)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, PIOBU_IN);
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci/*
13662306a36Sopenharmony_ci * sama5d2_piobu_direction_output() - gpiochip direction_output
13762306a36Sopenharmony_ci */
13862306a36Sopenharmony_cistatic int sama5d2_piobu_direction_output(struct gpio_chip *chip,
13962306a36Sopenharmony_ci					  unsigned int pin, int value)
14062306a36Sopenharmony_ci{
14162306a36Sopenharmony_ci	unsigned int val = PIOBU_OUT;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (value)
14462306a36Sopenharmony_ci		val |= PIOBU_HIGH;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION | PIOBU_SOD,
14762306a36Sopenharmony_ci					 val);
14862306a36Sopenharmony_ci}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci/*
15162306a36Sopenharmony_ci * sama5d2_piobu_get() - gpiochip get
15262306a36Sopenharmony_ci */
15362306a36Sopenharmony_cistatic int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin)
15462306a36Sopenharmony_ci{
15562306a36Sopenharmony_ci	/* if pin is input, read value from PDS else read from SOD */
15662306a36Sopenharmony_ci	int ret = sama5d2_piobu_get_direction(chip, pin);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (ret == GPIO_LINE_DIRECTION_IN)
15962306a36Sopenharmony_ci		ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS);
16062306a36Sopenharmony_ci	else if (ret == GPIO_LINE_DIRECTION_OUT)
16162306a36Sopenharmony_ci		ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	if (ret < 0)
16462306a36Sopenharmony_ci		return ret;
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	return !!ret;
16762306a36Sopenharmony_ci}
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci/*
17062306a36Sopenharmony_ci * sama5d2_piobu_set() - gpiochip set
17162306a36Sopenharmony_ci */
17262306a36Sopenharmony_cistatic void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin,
17362306a36Sopenharmony_ci			      int value)
17462306a36Sopenharmony_ci{
17562306a36Sopenharmony_ci	if (!value)
17662306a36Sopenharmony_ci		value = PIOBU_LOW;
17762306a36Sopenharmony_ci	else
17862306a36Sopenharmony_ci		value = PIOBU_HIGH;
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value);
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic int sama5d2_piobu_probe(struct platform_device *pdev)
18462306a36Sopenharmony_ci{
18562306a36Sopenharmony_ci	struct sama5d2_piobu *piobu;
18662306a36Sopenharmony_ci	int ret, i;
18762306a36Sopenharmony_ci
18862306a36Sopenharmony_ci	piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL);
18962306a36Sopenharmony_ci	if (!piobu)
19062306a36Sopenharmony_ci		return -ENOMEM;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	piobu->chip.label = pdev->name;
19362306a36Sopenharmony_ci	piobu->chip.parent = &pdev->dev;
19462306a36Sopenharmony_ci	piobu->chip.owner = THIS_MODULE,
19562306a36Sopenharmony_ci	piobu->chip.get_direction = sama5d2_piobu_get_direction,
19662306a36Sopenharmony_ci	piobu->chip.direction_input = sama5d2_piobu_direction_input,
19762306a36Sopenharmony_ci	piobu->chip.direction_output = sama5d2_piobu_direction_output,
19862306a36Sopenharmony_ci	piobu->chip.get = sama5d2_piobu_get,
19962306a36Sopenharmony_ci	piobu->chip.set = sama5d2_piobu_set,
20062306a36Sopenharmony_ci	piobu->chip.base = -1,
20162306a36Sopenharmony_ci	piobu->chip.ngpio = PIOBU_NUM,
20262306a36Sopenharmony_ci	piobu->chip.can_sleep = 0,
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node);
20562306a36Sopenharmony_ci	if (IS_ERR(piobu->regmap)) {
20662306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n",
20762306a36Sopenharmony_ci			PTR_ERR(piobu->regmap));
20862306a36Sopenharmony_ci		return PTR_ERR(piobu->regmap);
20962306a36Sopenharmony_ci	}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu);
21262306a36Sopenharmony_ci	if (ret) {
21362306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret);
21462306a36Sopenharmony_ci		return ret;
21562306a36Sopenharmony_ci	}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	for (i = 0; i < PIOBU_NUM; ++i) {
21862306a36Sopenharmony_ci		ret = sama5d2_piobu_setup_pin(&piobu->chip, i);
21962306a36Sopenharmony_ci		if (ret) {
22062306a36Sopenharmony_ci			dev_err(&pdev->dev, "Failed to setup pin: %d %d\n",
22162306a36Sopenharmony_ci				i, ret);
22262306a36Sopenharmony_ci			return ret;
22362306a36Sopenharmony_ci		}
22462306a36Sopenharmony_ci	}
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return 0;
22762306a36Sopenharmony_ci}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_cistatic const struct of_device_id sama5d2_piobu_ids[] = {
23062306a36Sopenharmony_ci	{ .compatible = "atmel,sama5d2-secumod" },
23162306a36Sopenharmony_ci	{},
23262306a36Sopenharmony_ci};
23362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sama5d2_piobu_ids);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_cistatic struct platform_driver sama5d2_piobu_driver = {
23662306a36Sopenharmony_ci	.driver = {
23762306a36Sopenharmony_ci		.name		= "sama5d2-piobu",
23862306a36Sopenharmony_ci		.of_match_table	= sama5d2_piobu_ids,
23962306a36Sopenharmony_ci	},
24062306a36Sopenharmony_ci	.probe = sama5d2_piobu_probe,
24162306a36Sopenharmony_ci};
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_cimodule_platform_driver(sama5d2_piobu_driver);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
24662306a36Sopenharmony_ciMODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver");
24762306a36Sopenharmony_ciMODULE_AUTHOR("Andrei Stefanescu <andrei.stefanescu@microchip.com>");
248