diff options
-rw-r--r-- | MAINTAINERS | 6 | ||||
-rw-r--r-- | drivers/gpio/Kconfig | 11 | ||||
-rw-r--r-- | drivers/gpio/Makefile | 1 | ||||
-rw-r--r-- | drivers/gpio/gpio-sama5d2-piobu.c | 253 |
4 files changed, 271 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 65828b79ae49..79979ca0edd4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -9768,6 +9768,12 @@ M: Nicolas Ferre <nicolas.ferre@microchip.com> | |||
9768 | S: Supported | 9768 | S: Supported |
9769 | F: drivers/power/reset/at91-sama5d2_shdwc.c | 9769 | F: drivers/power/reset/at91-sama5d2_shdwc.c |
9770 | 9770 | ||
9771 | MICROCHIP SAMA5D2-COMPATIBLE PIOBU GPIO | ||
9772 | M: Andrei Stefanescu <andrei.stefanescu@microchip.com> | ||
9773 | L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers) | ||
9774 | L: linux-gpio@vger.kernel.org | ||
9775 | F: drivers/gpio/gpio-sama5d2-piobu.c | ||
9776 | |||
9771 | MICROCHIP SPI DRIVER | 9777 | MICROCHIP SPI DRIVER |
9772 | M: Nicolas Ferre <nicolas.ferre@microchip.com> | 9778 | M: Nicolas Ferre <nicolas.ferre@microchip.com> |
9773 | S: Supported | 9779 | S: Supported |
diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 06e7f9d5b84a..f47994708809 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig | |||
@@ -430,6 +430,17 @@ config GPIO_REG | |||
430 | A 32-bit single register GPIO fixed in/out implementation. This | 430 | A 32-bit single register GPIO fixed in/out implementation. This |
431 | can be used to represent any register as a set of GPIO signals. | 431 | can be used to represent any register as a set of GPIO signals. |
432 | 432 | ||
433 | config GPIO_SAMA5D2_PIOBU | ||
434 | tristate "SAMA5D2 PIOBU GPIO support" | ||
435 | depends on MFD_SYSCON | ||
436 | select GPIO_SYSCON | ||
437 | help | ||
438 | Say yes here to use the PIOBU pins as GPIOs. | ||
439 | |||
440 | PIOBU pins on the SAMA5D2 can be used as GPIOs. | ||
441 | The difference from regular GPIOs is that they | ||
442 | maintain their value during backup/self-refresh. | ||
443 | |||
433 | config GPIO_SIOX | 444 | config GPIO_SIOX |
434 | tristate "SIOX GPIO support" | 445 | tristate "SIOX GPIO support" |
435 | depends on SIOX | 446 | depends on SIOX |
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index 671c4477c951..f18d34590722 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile | |||
@@ -108,6 +108,7 @@ obj-$(CONFIG_GPIO_RDC321X) += gpio-rdc321x.o | |||
108 | obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o | 108 | obj-$(CONFIG_GPIO_RCAR) += gpio-rcar.o |
109 | obj-$(CONFIG_GPIO_REG) += gpio-reg.o | 109 | obj-$(CONFIG_GPIO_REG) += gpio-reg.o |
110 | obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o | 110 | obj-$(CONFIG_ARCH_SA1100) += gpio-sa1100.o |
111 | obj-$(CONFIG_GPIO_SAMA5D2_PIOBU) += gpio-sama5d2-piobu.o | ||
111 | obj-$(CONFIG_GPIO_SCH) += gpio-sch.o | 112 | obj-$(CONFIG_GPIO_SCH) += gpio-sch.o |
112 | obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o | 113 | obj-$(CONFIG_GPIO_SCH311X) += gpio-sch311x.o |
113 | obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o | 114 | obj-$(CONFIG_GPIO_SNPS_CREG) += gpio-creg-snps.o |
diff --git a/drivers/gpio/gpio-sama5d2-piobu.c b/drivers/gpio/gpio-sama5d2-piobu.c new file mode 100644 index 000000000000..03a000659fa1 --- /dev/null +++ b/drivers/gpio/gpio-sama5d2-piobu.c | |||
@@ -0,0 +1,253 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * SAMA5D2 PIOBU GPIO controller | ||
4 | * | ||
5 | * Copyright (C) 2018 Microchip Technology Inc. and its subsidiaries | ||
6 | * | ||
7 | * Author: Andrei Stefanescu <andrei.stefanescu@microchip.com> | ||
8 | * | ||
9 | */ | ||
10 | #include <linux/bits.h> | ||
11 | #include <linux/gpio/driver.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/kernel.h> | ||
14 | #include <linux/mfd/syscon.h> | ||
15 | #include <linux/module.h> | ||
16 | #include <linux/of.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/regmap.h> | ||
19 | |||
20 | #define PIOBU_NUM 8 | ||
21 | #define PIOBU_REG_SIZE 4 | ||
22 | |||
23 | /* | ||
24 | * backup mode protection register for tamper detection | ||
25 | * normal mode protection register for tamper detection | ||
26 | * wakeup signal generation | ||
27 | */ | ||
28 | #define PIOBU_BMPR 0x7C | ||
29 | #define PIOBU_NMPR 0x80 | ||
30 | #define PIOBU_WKPR 0x90 | ||
31 | |||
32 | #define PIOBU_BASE 0x18 /* PIOBU offset from SECUMOD base register address. */ | ||
33 | |||
34 | #define PIOBU_DET_OFFSET 16 | ||
35 | |||
36 | /* In the datasheet this bit is called OUTPUT */ | ||
37 | #define PIOBU_DIRECTION BIT(8) | ||
38 | #define PIOBU_OUT BIT(8) | ||
39 | #define PIOBU_IN 0 | ||
40 | |||
41 | #define PIOBU_SOD BIT(9) | ||
42 | #define PIOBU_PDS BIT(10) | ||
43 | |||
44 | #define PIOBU_HIGH BIT(9) | ||
45 | #define PIOBU_LOW 0 | ||
46 | |||
47 | struct sama5d2_piobu { | ||
48 | struct gpio_chip chip; | ||
49 | struct regmap *regmap; | ||
50 | }; | ||
51 | |||
52 | /** | ||
53 | * sama5d2_piobu_setup_pin() - prepares a pin for set_direction call | ||
54 | * | ||
55 | * Do not consider pin for tamper detection (normal and backup modes) | ||
56 | * Do not consider pin as tamper wakeup interrupt source | ||
57 | */ | ||
58 | static int sama5d2_piobu_setup_pin(struct gpio_chip *chip, unsigned int pin) | ||
59 | { | ||
60 | int ret; | ||
61 | struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, | ||
62 | chip); | ||
63 | unsigned int mask = BIT(PIOBU_DET_OFFSET + pin); | ||
64 | |||
65 | ret = regmap_update_bits(piobu->regmap, PIOBU_BMPR, mask, 0); | ||
66 | if (ret) | ||
67 | return ret; | ||
68 | |||
69 | ret = regmap_update_bits(piobu->regmap, PIOBU_NMPR, mask, 0); | ||
70 | if (ret) | ||
71 | return ret; | ||
72 | |||
73 | return regmap_update_bits(piobu->regmap, PIOBU_WKPR, mask, 0); | ||
74 | } | ||
75 | |||
76 | /** | ||
77 | * sama5d2_piobu_write_value() - writes value & mask at the pin's PIOBU register | ||
78 | */ | ||
79 | static int sama5d2_piobu_write_value(struct gpio_chip *chip, unsigned int pin, | ||
80 | unsigned int mask, unsigned int value) | ||
81 | { | ||
82 | int reg; | ||
83 | struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, | ||
84 | chip); | ||
85 | |||
86 | reg = PIOBU_BASE + pin * PIOBU_REG_SIZE; | ||
87 | |||
88 | return regmap_update_bits(piobu->regmap, reg, mask, value); | ||
89 | } | ||
90 | |||
91 | /** | ||
92 | * sama5d2_piobu_read_value() - read the value with masking from the pin's PIOBU | ||
93 | * register | ||
94 | */ | ||
95 | static int sama5d2_piobu_read_value(struct gpio_chip *chip, unsigned int pin, | ||
96 | unsigned int mask) | ||
97 | { | ||
98 | struct sama5d2_piobu *piobu = container_of(chip, struct sama5d2_piobu, | ||
99 | chip); | ||
100 | unsigned int val, reg; | ||
101 | int ret; | ||
102 | |||
103 | reg = PIOBU_BASE + pin * PIOBU_REG_SIZE; | ||
104 | ret = regmap_read(piobu->regmap, reg, &val); | ||
105 | if (ret < 0) | ||
106 | return ret; | ||
107 | |||
108 | return val & mask; | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * sama5d2_piobu_set_direction() - mark pin as input or output | ||
113 | */ | ||
114 | static int sama5d2_piobu_set_direction(struct gpio_chip *chip, | ||
115 | unsigned int direction, | ||
116 | unsigned int pin) | ||
117 | { | ||
118 | return sama5d2_piobu_write_value(chip, pin, PIOBU_DIRECTION, direction); | ||
119 | } | ||
120 | |||
121 | /** | ||
122 | * sama5d2_piobu_get_direction() - gpiochip get_direction | ||
123 | */ | ||
124 | static int sama5d2_piobu_get_direction(struct gpio_chip *chip, | ||
125 | unsigned int pin) | ||
126 | { | ||
127 | int ret = sama5d2_piobu_read_value(chip, pin, PIOBU_DIRECTION); | ||
128 | |||
129 | if (ret < 0) | ||
130 | return ret; | ||
131 | |||
132 | return (ret == PIOBU_IN) ? 1 : 0; | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * sama5d2_piobu_direction_input() - gpiochip direction_input | ||
137 | */ | ||
138 | static int sama5d2_piobu_direction_input(struct gpio_chip *chip, | ||
139 | unsigned int pin) | ||
140 | { | ||
141 | return sama5d2_piobu_set_direction(chip, PIOBU_IN, pin); | ||
142 | } | ||
143 | |||
144 | /** | ||
145 | * sama5d2_piobu_direction_output() - gpiochip direction_output | ||
146 | */ | ||
147 | static int sama5d2_piobu_direction_output(struct gpio_chip *chip, | ||
148 | unsigned int pin, int value) | ||
149 | { | ||
150 | return sama5d2_piobu_set_direction(chip, PIOBU_OUT, pin); | ||
151 | } | ||
152 | |||
153 | /** | ||
154 | * sama5d2_piobu_get() - gpiochip get | ||
155 | */ | ||
156 | static int sama5d2_piobu_get(struct gpio_chip *chip, unsigned int pin) | ||
157 | { | ||
158 | /* if pin is input, read value from PDS else read from SOD */ | ||
159 | int ret = sama5d2_piobu_get_direction(chip, pin); | ||
160 | |||
161 | if (ret == 1) | ||
162 | ret = sama5d2_piobu_read_value(chip, pin, PIOBU_PDS); | ||
163 | else if (!ret) | ||
164 | ret = sama5d2_piobu_read_value(chip, pin, PIOBU_SOD); | ||
165 | |||
166 | if (ret < 0) | ||
167 | return ret; | ||
168 | |||
169 | return !!ret; | ||
170 | } | ||
171 | |||
172 | /** | ||
173 | * sama5d2_piobu_set() - gpiochip set | ||
174 | */ | ||
175 | static void sama5d2_piobu_set(struct gpio_chip *chip, unsigned int pin, | ||
176 | int value) | ||
177 | { | ||
178 | if (!value) | ||
179 | value = PIOBU_LOW; | ||
180 | else | ||
181 | value = PIOBU_HIGH; | ||
182 | |||
183 | sama5d2_piobu_write_value(chip, pin, PIOBU_SOD, value); | ||
184 | } | ||
185 | |||
186 | static int sama5d2_piobu_probe(struct platform_device *pdev) | ||
187 | { | ||
188 | struct sama5d2_piobu *piobu; | ||
189 | int ret, i; | ||
190 | |||
191 | piobu = devm_kzalloc(&pdev->dev, sizeof(*piobu), GFP_KERNEL); | ||
192 | if (!piobu) | ||
193 | return -ENOMEM; | ||
194 | |||
195 | platform_set_drvdata(pdev, piobu); | ||
196 | piobu->chip.label = pdev->name; | ||
197 | piobu->chip.parent = &pdev->dev; | ||
198 | piobu->chip.of_node = pdev->dev.of_node; | ||
199 | piobu->chip.owner = THIS_MODULE, | ||
200 | piobu->chip.get_direction = sama5d2_piobu_get_direction, | ||
201 | piobu->chip.direction_input = sama5d2_piobu_direction_input, | ||
202 | piobu->chip.direction_output = sama5d2_piobu_direction_output, | ||
203 | piobu->chip.get = sama5d2_piobu_get, | ||
204 | piobu->chip.set = sama5d2_piobu_set, | ||
205 | piobu->chip.base = -1, | ||
206 | piobu->chip.ngpio = PIOBU_NUM, | ||
207 | piobu->chip.can_sleep = 0, | ||
208 | |||
209 | piobu->regmap = syscon_node_to_regmap(pdev->dev.of_node); | ||
210 | if (IS_ERR(piobu->regmap)) { | ||
211 | dev_err(&pdev->dev, "Failed to get syscon regmap %ld\n", | ||
212 | PTR_ERR(piobu->regmap)); | ||
213 | return PTR_ERR(piobu->regmap); | ||
214 | } | ||
215 | |||
216 | ret = devm_gpiochip_add_data(&pdev->dev, &piobu->chip, piobu); | ||
217 | if (ret) { | ||
218 | dev_err(&pdev->dev, "Failed to add gpiochip %d\n", ret); | ||
219 | return ret; | ||
220 | } | ||
221 | |||
222 | for (i = 0; i < PIOBU_NUM; ++i) { | ||
223 | ret = sama5d2_piobu_setup_pin(&piobu->chip, i); | ||
224 | if (ret) { | ||
225 | dev_err(&pdev->dev, "Failed to setup pin: %d %d\n", | ||
226 | i, ret); | ||
227 | return ret; | ||
228 | } | ||
229 | } | ||
230 | |||
231 | return 0; | ||
232 | } | ||
233 | |||
234 | static const struct of_device_id sama5d2_piobu_ids[] = { | ||
235 | { .compatible = "atmel,sama5d2-secumod" }, | ||
236 | {}, | ||
237 | }; | ||
238 | MODULE_DEVICE_TABLE(of, sama5d2_piobu_ids); | ||
239 | |||
240 | static struct platform_driver sama5d2_piobu_driver = { | ||
241 | .driver = { | ||
242 | .name = "sama5d2-piobu", | ||
243 | .of_match_table = of_match_ptr(sama5d2_piobu_ids) | ||
244 | }, | ||
245 | .probe = sama5d2_piobu_probe, | ||
246 | }; | ||
247 | |||
248 | module_platform_driver(sama5d2_piobu_driver); | ||
249 | |||
250 | MODULE_VERSION("1.0"); | ||
251 | MODULE_LICENSE("GPL v2"); | ||
252 | MODULE_DESCRIPTION("SAMA5D2 PIOBU controller driver"); | ||
253 | MODULE_AUTHOR("Andrei Stefanescu <andrei.stefanescu@microchip.com>"); | ||