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_set_direction() - mark pin as input or output
113fb0b35d3SAndrei.Stefanescu@microchip.com  */
114fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_set_direction(struct gpio_chip *chip,
115fb0b35d3SAndrei.Stefanescu@microchip.com 				       unsigned int direction,
116fb0b35d3SAndrei.Stefanescu@microchip.com 				       unsigned int pin)
117fb0b35d3SAndrei.Stefanescu@microchip.com {
118fb0b35d3SAndrei.Stefanescu@microchip.com 	return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, direction);
119fb0b35d3SAndrei.Stefanescu@microchip.com }
120fb0b35d3SAndrei.Stefanescu@microchip.com 
121fb0b35d3SAndrei.Stefanescu@microchip.com /**
122fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_get_direction() - gpiochip get_direction
123fb0b35d3SAndrei.Stefanescu@microchip.com  */
124fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_get_direction(struct gpio_chip *chip,
125fb0b35d3SAndrei.Stefanescu@microchip.com 				       unsigned int pin)
126fb0b35d3SAndrei.Stefanescu@microchip.com {
127fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION);
128fb0b35d3SAndrei.Stefanescu@microchip.com 
129fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret < 0)
130fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
131fb0b35d3SAndrei.Stefanescu@microchip.com 
132fb0b35d3SAndrei.Stefanescu@microchip.com 	return (ret == PIOBU_IN) ? 1 : 0;
133fb0b35d3SAndrei.Stefanescu@microchip.com }
134fb0b35d3SAndrei.Stefanescu@microchip.com 
135fb0b35d3SAndrei.Stefanescu@microchip.com /**
136fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_direction_input() - gpiochip direction_input
137fb0b35d3SAndrei.Stefanescu@microchip.com  */
138fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_direction_input(struct gpio_chip *chip,
139fb0b35d3SAndrei.Stefanescu@microchip.com 					 unsigned int pin)
140fb0b35d3SAndrei.Stefanescu@microchip.com {
141fb0b35d3SAndrei.Stefanescu@microchip.com 	return sama5d2_piobu_set_direction(chip, PIOBU_IN, pin);
142fb0b35d3SAndrei.Stefanescu@microchip.com }
143fb0b35d3SAndrei.Stefanescu@microchip.com 
144fb0b35d3SAndrei.Stefanescu@microchip.com /**
145fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_direction_output() - gpiochip direction_output
146fb0b35d3SAndrei.Stefanescu@microchip.com  */
147fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_direction_output(struct gpio_chip *chip,
148fb0b35d3SAndrei.Stefanescu@microchip.com 					  unsigned int pin, int value)
149fb0b35d3SAndrei.Stefanescu@microchip.com {
150fb0b35d3SAndrei.Stefanescu@microchip.com 	return sama5d2_piobu_set_direction(chip, PIOBU_OUT, pin);
151fb0b35d3SAndrei.Stefanescu@microchip.com }
152fb0b35d3SAndrei.Stefanescu@microchip.com 
153fb0b35d3SAndrei.Stefanescu@microchip.com /**
154fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_get() - gpiochip get
155fb0b35d3SAndrei.Stefanescu@microchip.com  */
156fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin)
157fb0b35d3SAndrei.Stefanescu@microchip.com {
158fb0b35d3SAndrei.Stefanescu@microchip.com 	/* if pin is input, read value from PDS else read from SOD */
159fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret = sama5d2_piobu_get_direction(chip, pin);
160fb0b35d3SAndrei.Stefanescu@microchip.com 
161fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret == 1)
162fb0b35d3SAndrei.Stefanescu@microchip.com 		ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS);
163fb0b35d3SAndrei.Stefanescu@microchip.com 	else if (!ret)
164fb0b35d3SAndrei.Stefanescu@microchip.com 		ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD);
165fb0b35d3SAndrei.Stefanescu@microchip.com 
166fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret < 0)
167fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
168fb0b35d3SAndrei.Stefanescu@microchip.com 
169fb0b35d3SAndrei.Stefanescu@microchip.com 	return !!ret;
170fb0b35d3SAndrei.Stefanescu@microchip.com }
171fb0b35d3SAndrei.Stefanescu@microchip.com 
172fb0b35d3SAndrei.Stefanescu@microchip.com /**
173fb0b35d3SAndrei.Stefanescu@microchip.com  * sama5d2_piobu_set() - gpiochip set
174fb0b35d3SAndrei.Stefanescu@microchip.com  */
175fb0b35d3SAndrei.Stefanescu@microchip.com static void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin,
176fb0b35d3SAndrei.Stefanescu@microchip.com 			      int value)
177fb0b35d3SAndrei.Stefanescu@microchip.com {
178fb0b35d3SAndrei.Stefanescu@microchip.com 	if (!value)
179fb0b35d3SAndrei.Stefanescu@microchip.com 		value = PIOBU_LOW;
180fb0b35d3SAndrei.Stefanescu@microchip.com 	else
181fb0b35d3SAndrei.Stefanescu@microchip.com 		value = PIOBU_HIGH;
182fb0b35d3SAndrei.Stefanescu@microchip.com 
183fb0b35d3SAndrei.Stefanescu@microchip.com 	sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value);
184fb0b35d3SAndrei.Stefanescu@microchip.com }
185fb0b35d3SAndrei.Stefanescu@microchip.com 
186fb0b35d3SAndrei.Stefanescu@microchip.com static int sama5d2_piobu_probe(struct platform_device *pdev)
187fb0b35d3SAndrei.Stefanescu@microchip.com {
188fb0b35d3SAndrei.Stefanescu@microchip.com 	struct sama5d2_piobu *piobu;
189fb0b35d3SAndrei.Stefanescu@microchip.com 	int ret, i;
190fb0b35d3SAndrei.Stefanescu@microchip.com 
191fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL);
192fb0b35d3SAndrei.Stefanescu@microchip.com 	if (!piobu)
193fb0b35d3SAndrei.Stefanescu@microchip.com 		return -ENOMEM;
194fb0b35d3SAndrei.Stefanescu@microchip.com 
195fb0b35d3SAndrei.Stefanescu@microchip.com 	platform_set_drvdata(pdev, piobu);
196fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.label = pdev->name;
197fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.parent = &pdev->dev;
198fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.of_node = pdev->dev.of_node;
199fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.owner = THIS_MODULE,
200fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.get_direction = sama5d2_piobu_get_direction,
201fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.direction_input = sama5d2_piobu_direction_input,
202fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.direction_output = sama5d2_piobu_direction_output,
203fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.get = sama5d2_piobu_get,
204fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.set = sama5d2_piobu_set,
205fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.base = -1,
206fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.ngpio = PIOBU_NUM,
207fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->chip.can_sleep = 0,
208fb0b35d3SAndrei.Stefanescu@microchip.com 
209fb0b35d3SAndrei.Stefanescu@microchip.com 	piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node);
210fb0b35d3SAndrei.Stefanescu@microchip.com 	if (IS_ERR(piobu->regmap)) {
211fb0b35d3SAndrei.Stefanescu@microchip.com 		dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n",
212fb0b35d3SAndrei.Stefanescu@microchip.com 			PTR_ERR(piobu->regmap));
213fb0b35d3SAndrei.Stefanescu@microchip.com 		return PTR_ERR(piobu->regmap);
214fb0b35d3SAndrei.Stefanescu@microchip.com 	}
215fb0b35d3SAndrei.Stefanescu@microchip.com 
216fb0b35d3SAndrei.Stefanescu@microchip.com 	ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu);
217fb0b35d3SAndrei.Stefanescu@microchip.com 	if (ret) {
218fb0b35d3SAndrei.Stefanescu@microchip.com 		dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret);
219fb0b35d3SAndrei.Stefanescu@microchip.com 		return ret;
220fb0b35d3SAndrei.Stefanescu@microchip.com 	}
221fb0b35d3SAndrei.Stefanescu@microchip.com 
222fb0b35d3SAndrei.Stefanescu@microchip.com 	for (i = 0; i < PIOBU_NUM; ++i) {
223fb0b35d3SAndrei.Stefanescu@microchip.com 		ret = sama5d2_piobu_setup_pin(&piobu->chip, i);
224fb0b35d3SAndrei.Stefanescu@microchip.com 		if (ret) {
225fb0b35d3SAndrei.Stefanescu@microchip.com 			dev_err(&pdev->dev, "Failed to setup pin: %d %d\n",
226fb0b35d3SAndrei.Stefanescu@microchip.com 				i, ret);
227fb0b35d3SAndrei.Stefanescu@microchip.com 			return ret;
228fb0b35d3SAndrei.Stefanescu@microchip.com 		}
229fb0b35d3SAndrei.Stefanescu@microchip.com 	}
230fb0b35d3SAndrei.Stefanescu@microchip.com 
231fb0b35d3SAndrei.Stefanescu@microchip.com 	return 0;
232fb0b35d3SAndrei.Stefanescu@microchip.com }
233fb0b35d3SAndrei.Stefanescu@microchip.com 
234fb0b35d3SAndrei.Stefanescu@microchip.com static const struct of_device_id sama5d2_piobu_ids[] = {
235fb0b35d3SAndrei.Stefanescu@microchip.com 	{ .compatible = "atmel,sama5d2-secumod" },
236fb0b35d3SAndrei.Stefanescu@microchip.com 	{},
237fb0b35d3SAndrei.Stefanescu@microchip.com };
238fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_DEVICE_TABLE(of, sama5d2_piobu_ids);
239fb0b35d3SAndrei.Stefanescu@microchip.com 
240fb0b35d3SAndrei.Stefanescu@microchip.com static struct platform_driver sama5d2_piobu_driver = {
241fb0b35d3SAndrei.Stefanescu@microchip.com 	.driver = {
242fb0b35d3SAndrei.Stefanescu@microchip.com 		.name		= "sama5d2-piobu",
243fb0b35d3SAndrei.Stefanescu@microchip.com 		.of_match_table	= of_match_ptr(sama5d2_piobu_ids)
244fb0b35d3SAndrei.Stefanescu@microchip.com 	},
245fb0b35d3SAndrei.Stefanescu@microchip.com 	.probe = sama5d2_piobu_probe,
246fb0b35d3SAndrei.Stefanescu@microchip.com };
247fb0b35d3SAndrei.Stefanescu@microchip.com 
248fb0b35d3SAndrei.Stefanescu@microchip.com module_platform_driver(sama5d2_piobu_driver);
249fb0b35d3SAndrei.Stefanescu@microchip.com 
250fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_VERSION("1.0");
251fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_LICENSE("GPL v2");
252fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver");
253fb0b35d3SAndrei.Stefanescu@microchip.com MODULE_AUTHOR("Andrei Stefanescu <andrei.stefanescu@microchip.com>");
254