1fb0b35d3SAndrei.Stefanescu@microchip.com // SPDX-License-Identifier: GPL-2.0
2fb0b35d3SAndrei.Stefanescu@microchip.com /*
3fb0b35d3SAndrei.Stefanescu@microchip.com  * SAMA5D2 PIOBU GPIO controller
4fb0b35d3SAndrei.Stefanescu@microchip.com  *
5fb0b35d3SAndrei.Stefanescu@microchip.com  * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries
6fb0b35d3SAndrei.Stefanescu@microchip.com  *
7fb0b35d3SAndrei.Stefanescu@microchip.com  * Author: Andrei Stefanescu <andrei.stefanescu@microchip.com>
8fb0b35d3SAndrei.Stefanescu@microchip.com  *
9fb0b35d3SAndrei.Stefanescu@microchip.com  */
10fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/bits.h>
11fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/gpio/driver.h>
12fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/init.h>
13fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/kernel.h>
14fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/mfd/syscon.h>
15fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/module.h>
16fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/of.h>
17fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/platform_device.h>
18fb0b35d3SAndrei.Stefanescu@microchip.com #include <linux/regmap.h>
19fb0b35d3SAndrei.Stefanescu@microchip.com 
20fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_NUM 8
21fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_REG_SIZE 4
22fb0b35d3SAndrei.Stefanescu@microchip.com 
23fb0b35d3SAndrei.Stefanescu@microchip.com /*
24fb0b35d3SAndrei.Stefanescu@microchip.com  * backup mode protection register for tamper detection
25fb0b35d3SAndrei.Stefanescu@microchip.com  * normal mode protection register for tamper detection
26fb0b35d3SAndrei.Stefanescu@microchip.com  * wakeup signal generation
27fb0b35d3SAndrei.Stefanescu@microchip.com  */
28fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_BMPR 0x7C
29fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_NMPR 0x80
30fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_WKPR 0x90
31fb0b35d3SAndrei.Stefanescu@microchip.com 
32fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_BASE 0x18 /* PIOBU offset from SECUMOD base register address. */
33fb0b35d3SAndrei.Stefanescu@microchip.com 
34fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_DET_OFFSET 16
35fb0b35d3SAndrei.Stefanescu@microchip.com 
36fb0b35d3SAndrei.Stefanescu@microchip.com /* In the datasheet this bit is called OUTPUT */
37fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_DIRECTION BIT(8)
38fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_OUT BIT(8)
39fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_IN 0
40fb0b35d3SAndrei.Stefanescu@microchip.com 
41fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_SOD BIT(9)
42fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_PDS BIT(10)
43fb0b35d3SAndrei.Stefanescu@microchip.com 
44fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_HIGH BIT(9)
45fb0b35d3SAndrei.Stefanescu@microchip.com #define PIOBU_LOW 0
46fb0b35d3SAndrei.Stefanescu@microchip.com 
47fb0b35d3SAndrei.Stefanescu@microchip.com struct sama5d2_piobu {
48fb0b35d3SAndrei.Stefanescu@microchip.com 	struct gpio_chip chip;
49fb0b35d3SAndrei.Stefanescu@microchip.com 	struct regmap *regmap;
50fb0b35d3SAndrei.Stefanescu@microchip.com };
51fb0b35d3SAndrei.Stefanescu@microchip.com 
52fb0b35d3SAndrei.Stefanescu@microchip.com /**
53fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_setup_pin() - prepares a pin for set_direction call
54fb0b35d3SAndrei.Stefanescu@microchip.com  *
55fb0b35d3SAndrei.Stefanescu@microchip.com  * Do not consider pin for tamper detection (normal and backup modes)
56fb0b35d3SAndrei.Stefanescu@microchip.com  * Do not consider pin as tamper wakeup interrupt source
57fb0b35d3SAndrei.Stefanescu@microchip.com  */
58fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_setup_pin(struct gpio_chip *chip, unsigned int pin)
59fb0b35d3SAndrei.Stefanescu@microchip.com {
60fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret;
61fb0b35d3SAndrei.Stefanescu@microchip.com 	struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
62fb0b35d3SAndrei.Stefanescu@microchip.com 						   chip);
63fb0b35d3SAndrei.Stefanescu@microchip.com 	unsigned int mask = BIT(PIOBU_DET_OFFSET + pin);
64fb0b35d3SAndrei.Stefanescu@microchip.com 
65fb0b35d3SAndrei.Stefanescu@microchip.com 	ret = regmap_update_bits(piobu->regmap, PIOBU_BMPR, mask, 0);
66fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret)
67fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
68fb0b35d3SAndrei.Stefanescu@microchip.com 
69fb0b35d3SAndrei.Stefanescu@microchip.com 	ret = regmap_update_bits(piobu->regmap, PIOBU_NMPR, mask, 0);
70fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret)
71fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
72fb0b35d3SAndrei.Stefanescu@microchip.com 
73fb0b35d3SAndrei.Stefanescu@microchip.com 	return regmap_update_bits(piobu->regmap, PIOBU_WKPR, mask, 0);
74fb0b35d3SAndrei.Stefanescu@microchip.com }
75fb0b35d3SAndrei.Stefanescu@microchip.com 
76fb0b35d3SAndrei.Stefanescu@microchip.com /**
77fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_write_value() - writes value & mask at the pin's PIOBU register
78fb0b35d3SAndrei.Stefanescu@microchip.com  */
79fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_write_value(struct gpio_chip *chip, unsigned int pin,
80fb0b35d3SAndrei.Stefanescu@microchip.com 				     unsigned int mask, unsigned int value)
81fb0b35d3SAndrei.Stefanescu@microchip.com {
82fb0b35d3SAndrei.Stefanescu@microchip.com 	int reg;
83fb0b35d3SAndrei.Stefanescu@microchip.com 	struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
84fb0b35d3SAndrei.Stefanescu@microchip.com 						   chip);
85fb0b35d3SAndrei.Stefanescu@microchip.com 
86fb0b35d3SAndrei.Stefanescu@microchip.com 	reg = PIOBU_BASE + pin * PIOBU_REG_SIZE;
87fb0b35d3SAndrei.Stefanescu@microchip.com 
88fb0b35d3SAndrei.Stefanescu@microchip.com 	return regmap_update_bits(piobu->regmap, reg, mask, value);
89fb0b35d3SAndrei.Stefanescu@microchip.com }
90fb0b35d3SAndrei.Stefanescu@microchip.com 
91fb0b35d3SAndrei.Stefanescu@microchip.com /**
92fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_read_value() - read the value with masking from the pin's PIOBU
93fb0b35d3SAndrei.Stefanescu@microchip.com  *			      register
94fb0b35d3SAndrei.Stefanescu@microchip.com  */
95fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_read_value(struct gpio_chip *chip, unsigned int pin,
96fb0b35d3SAndrei.Stefanescu@microchip.com 				    unsigned int mask)
97fb0b35d3SAndrei.Stefanescu@microchip.com {
98fb0b35d3SAndrei.Stefanescu@microchip.com 	struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu,
99fb0b35d3SAndrei.Stefanescu@microchip.com 						   chip);
100fb0b35d3SAndrei.Stefanescu@microchip.com 	unsigned int val, reg;
101fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret;
102fb0b35d3SAndrei.Stefanescu@microchip.com 
103fb0b35d3SAndrei.Stefanescu@microchip.com 	reg = PIOBU_BASE + pin * PIOBU_REG_SIZE;
104fb0b35d3SAndrei.Stefanescu@microchip.com 	ret = regmap_read(piobu->regmap, reg, &val);
105fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret < 0)
106fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
107fb0b35d3SAndrei.Stefanescu@microchip.com 
108fb0b35d3SAndrei.Stefanescu@microchip.com 	return val & mask;
109fb0b35d3SAndrei.Stefanescu@microchip.com }
110fb0b35d3SAndrei.Stefanescu@microchip.com 
111fb0b35d3SAndrei.Stefanescu@microchip.com /**
112fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_get_direction() - gpiochip get_direction
113fb0b35d3SAndrei.Stefanescu@microchip.com  */
114fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_get_direction(struct gpio_chip *chip,
115fb0b35d3SAndrei.Stefanescu@microchip.com 				       unsigned int pin)
116fb0b35d3SAndrei.Stefanescu@microchip.com {
117fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION);
118fb0b35d3SAndrei.Stefanescu@microchip.com 
119fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret < 0)
120fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
121fb0b35d3SAndrei.Stefanescu@microchip.com 
122fb0b35d3SAndrei.Stefanescu@microchip.com 	return (ret == PIOBU_IN) ? 1 : 0;
123fb0b35d3SAndrei.Stefanescu@microchip.com }
124fb0b35d3SAndrei.Stefanescu@microchip.com 
125fb0b35d3SAndrei.Stefanescu@microchip.com /**
126fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_direction_input() - gpiochip direction_input
127fb0b35d3SAndrei.Stefanescu@microchip.com  */
128fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_direction_input(struct gpio_chip *chip,
129fb0b35d3SAndrei.Stefanescu@microchip.com 					 unsigned int pin)
130fb0b35d3SAndrei.Stefanescu@microchip.com {
131e4889362SAxel Lin 	return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, PIOBU_IN);
132fb0b35d3SAndrei.Stefanescu@microchip.com }
133fb0b35d3SAndrei.Stefanescu@microchip.com 
134fb0b35d3SAndrei.Stefanescu@microchip.com /**
135fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_direction_output() - gpiochip direction_output
136fb0b35d3SAndrei.Stefanescu@microchip.com  */
137fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_direction_output(struct gpio_chip *chip,
138fb0b35d3SAndrei.Stefanescu@microchip.com 					  unsigned int pin, int value)
139fb0b35d3SAndrei.Stefanescu@microchip.com {
140e4889362SAxel Lin 	unsigned int val = PIOBU_OUT;
141e4889362SAxel Lin 
142e4889362SAxel Lin 	if (value)
143e4889362SAxel Lin 		val |= PIOBU_HIGH;
144e4889362SAxel Lin 
145e4889362SAxel Lin 	return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION | PIOBU_SOD,
146e4889362SAxel Lin 					 val);
147fb0b35d3SAndrei.Stefanescu@microchip.com }
148fb0b35d3SAndrei.Stefanescu@microchip.com 
149fb0b35d3SAndrei.Stefanescu@microchip.com /**
150fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_get() - gpiochip get
151fb0b35d3SAndrei.Stefanescu@microchip.com  */
152fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin)
153fb0b35d3SAndrei.Stefanescu@microchip.com {
154fb0b35d3SAndrei.Stefanescu@microchip.com 	/* if pin is input, read value from PDS else read from SOD */
155fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret = sama5d2_piobu_get_direction(chip, pin);
156fb0b35d3SAndrei.Stefanescu@microchip.com 
157fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret == 1)
158fb0b35d3SAndrei.Stefanescu@microchip.com 		ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS);
159fb0b35d3SAndrei.Stefanescu@microchip.com 	else if (!ret)
160fb0b35d3SAndrei.Stefanescu@microchip.com 		ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD);
161fb0b35d3SAndrei.Stefanescu@microchip.com 
162fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret < 0)
163fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
164fb0b35d3SAndrei.Stefanescu@microchip.com 
165fb0b35d3SAndrei.Stefanescu@microchip.com 	return !!ret;
166fb0b35d3SAndrei.Stefanescu@microchip.com }
167fb0b35d3SAndrei.Stefanescu@microchip.com 
168fb0b35d3SAndrei.Stefanescu@microchip.com /**
169fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_set() - gpiochip set
170fb0b35d3SAndrei.Stefanescu@microchip.com  */
171fb0b35d3SAndrei.Stefanescu@microchip.com static void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin,
172fb0b35d3SAndrei.Stefanescu@microchip.com 			      int value)
173fb0b35d3SAndrei.Stefanescu@microchip.com {
174fb0b35d3SAndrei.Stefanescu@microchip.com 	if (!value)
175fb0b35d3SAndrei.Stefanescu@microchip.com 		value = PIOBU_LOW;
176fb0b35d3SAndrei.Stefanescu@microchip.com 	else
177fb0b35d3SAndrei.Stefanescu@microchip.com 		value = PIOBU_HIGH;
178fb0b35d3SAndrei.Stefanescu@microchip.com 
179fb0b35d3SAndrei.Stefanescu@microchip.com 	sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value);
180fb0b35d3SAndrei.Stefanescu@microchip.com }
181fb0b35d3SAndrei.Stefanescu@microchip.com 
182fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_probe(struct platform_device *pdev)
183fb0b35d3SAndrei.Stefanescu@microchip.com {
184fb0b35d3SAndrei.Stefanescu@microchip.com 	struct sama5d2_piobu *piobu;
185fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret, i;
186fb0b35d3SAndrei.Stefanescu@microchip.com 
187fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL);
188fb0b35d3SAndrei.Stefanescu@microchip.com 	if (!piobu)
189fb0b35d3SAndrei.Stefanescu@microchip.com 		return -ENOMEM;
190fb0b35d3SAndrei.Stefanescu@microchip.com 
191fb0b35d3SAndrei.Stefanescu@microchip.com 	platform_set_drvdata(pdev, piobu);
192fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.label = pdev->name;
193fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.parent = &pdev->dev;
194fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.of_node = pdev->dev.of_node;
195fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.owner = THIS_MODULE,
196fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.get_direction = sama5d2_piobu_get_direction,
197fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.direction_input = sama5d2_piobu_direction_input,
198fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.direction_output = sama5d2_piobu_direction_output,
199fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.get = sama5d2_piobu_get,
200fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.set = sama5d2_piobu_set,
201fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.base = -1,
202fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.ngpio = PIOBU_NUM,
203fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.can_sleep = 0,
204fb0b35d3SAndrei.Stefanescu@microchip.com 
205fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node);
206fb0b35d3SAndrei.Stefanescu@microchip.com 	if (IS_ERR(piobu->regmap)) {
207fb0b35d3SAndrei.Stefanescu@microchip.com 		dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n",
208fb0b35d3SAndrei.Stefanescu@microchip.com 			PTR_ERR(piobu->regmap));
209fb0b35d3SAndrei.Stefanescu@microchip.com 		return PTR_ERR(piobu->regmap);
210fb0b35d3SAndrei.Stefanescu@microchip.com 	}
211fb0b35d3SAndrei.Stefanescu@microchip.com 
212fb0b35d3SAndrei.Stefanescu@microchip.com 	ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu);
213fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret) {
214fb0b35d3SAndrei.Stefanescu@microchip.com 		dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret);
215fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
216fb0b35d3SAndrei.Stefanescu@microchip.com 	}
217fb0b35d3SAndrei.Stefanescu@microchip.com 
218fb0b35d3SAndrei.Stefanescu@microchip.com 	for (i = 0; i < PIOBU_NUM; ++i) {
219fb0b35d3SAndrei.Stefanescu@microchip.com 		ret = sama5d2_piobu_setup_pin(&piobu->chip, i);
220fb0b35d3SAndrei.Stefanescu@microchip.com 		if (ret) {
221fb0b35d3SAndrei.Stefanescu@microchip.com 			dev_err(&pdev->dev, "Failed to setup pin: %d %d\n",
222fb0b35d3SAndrei.Stefanescu@microchip.com 				i, ret);
223fb0b35d3SAndrei.Stefanescu@microchip.com 			return ret;
224fb0b35d3SAndrei.Stefanescu@microchip.com 		}
225fb0b35d3SAndrei.Stefanescu@microchip.com 	}
226fb0b35d3SAndrei.Stefanescu@microchip.com 
227fb0b35d3SAndrei.Stefanescu@microchip.com 	return 0;
228fb0b35d3SAndrei.Stefanescu@microchip.com }
229fb0b35d3SAndrei.Stefanescu@microchip.com 
230fb0b35d3SAndrei.Stefanescu@microchip.com static const struct of_device_id sama5d2_piobu_ids[] = {
231fb0b35d3SAndrei.Stefanescu@microchip.com 	{ .compatible = "atmel,sama5d2-secumod" },
232fb0b35d3SAndrei.Stefanescu@microchip.com 	{},
233fb0b35d3SAndrei.Stefanescu@microchip.com };
234fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_DEVICE_TABLE(of, sama5d2_piobu_ids);
235fb0b35d3SAndrei.Stefanescu@microchip.com 
236fb0b35d3SAndrei.Stefanescu@microchip.com static struct platform_driver sama5d2_piobu_driver = {
237fb0b35d3SAndrei.Stefanescu@microchip.com 	.driver = {
238fb0b35d3SAndrei.Stefanescu@microchip.com 		.name		= "sama5d2-piobu",
239fb0b35d3SAndrei.Stefanescu@microchip.com 		.of_match_table	= of_match_ptr(sama5d2_piobu_ids)
240fb0b35d3SAndrei.Stefanescu@microchip.com 	},
241fb0b35d3SAndrei.Stefanescu@microchip.com 	.probe = sama5d2_piobu_probe,
242fb0b35d3SAndrei.Stefanescu@microchip.com };
243fb0b35d3SAndrei.Stefanescu@microchip.com 
244fb0b35d3SAndrei.Stefanescu@microchip.com module_platform_driver(sama5d2_piobu_driver);
245fb0b35d3SAndrei.Stefanescu@microchip.com 
246fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_VERSION("1.0");
247fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_LICENSE("GPL v2");
248fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver");
249fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_AUTHOR("Andrei Stefanescu <andrei.stefanescu@microchip.com>");
250