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