diff options
Diffstat (limited to 'drivers/gpio/gpio-syscon.c')
-rw-r--r-- | drivers/gpio/gpio-syscon.c | 191 |
1 files changed, 191 insertions, 0 deletions
diff --git a/drivers/gpio/gpio-syscon.c b/drivers/gpio/gpio-syscon.c new file mode 100644 index 000000000000..b50fe1297748 --- /dev/null +++ b/drivers/gpio/gpio-syscon.c | |||
@@ -0,0 +1,191 @@ | |||
1 | /* | ||
2 | * SYSCON GPIO driver | ||
3 | * | ||
4 | * Copyright (C) 2014 Alexander Shiyan <shc_work@mail.ru> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify | ||
7 | * it under the terms of the GNU General Public License as published by | ||
8 | * the Free Software Foundation; either version 2 of the License, or | ||
9 | * (at your option) any later version. | ||
10 | */ | ||
11 | |||
12 | #include <linux/err.h> | ||
13 | #include <linux/gpio.h> | ||
14 | #include <linux/module.h> | ||
15 | #include <linux/of.h> | ||
16 | #include <linux/of_device.h> | ||
17 | #include <linux/platform_device.h> | ||
18 | #include <linux/regmap.h> | ||
19 | #include <linux/mfd/syscon.h> | ||
20 | |||
21 | #define GPIO_SYSCON_FEAT_IN BIT(0) | ||
22 | #define GPIO_SYSCON_FEAT_OUT BIT(1) | ||
23 | #define GPIO_SYSCON_FEAT_DIR BIT(2) | ||
24 | |||
25 | /* SYSCON driver is designed to use 32-bit wide registers */ | ||
26 | #define SYSCON_REG_SIZE (4) | ||
27 | #define SYSCON_REG_BITS (SYSCON_REG_SIZE * 8) | ||
28 | |||
29 | /** | ||
30 | * struct syscon_gpio_data - Configuration for the device. | ||
31 | * compatible: SYSCON driver compatible string. | ||
32 | * flags: Set of GPIO_SYSCON_FEAT_ flags: | ||
33 | * GPIO_SYSCON_FEAT_IN: GPIOs supports input, | ||
34 | * GPIO_SYSCON_FEAT_OUT: GPIOs supports output, | ||
35 | * GPIO_SYSCON_FEAT_DIR: GPIOs supports switch direction. | ||
36 | * bit_count: Number of bits used as GPIOs. | ||
37 | * dat_bit_offset: Offset (in bits) to the first GPIO bit. | ||
38 | * dir_bit_offset: Optional offset (in bits) to the first bit to switch | ||
39 | * GPIO direction (Used with GPIO_SYSCON_FEAT_DIR flag). | ||
40 | */ | ||
41 | |||
42 | struct syscon_gpio_data { | ||
43 | const char *compatible; | ||
44 | unsigned int flags; | ||
45 | unsigned int bit_count; | ||
46 | unsigned int dat_bit_offset; | ||
47 | unsigned int dir_bit_offset; | ||
48 | }; | ||
49 | |||
50 | struct syscon_gpio_priv { | ||
51 | struct gpio_chip chip; | ||
52 | struct regmap *syscon; | ||
53 | const struct syscon_gpio_data *data; | ||
54 | }; | ||
55 | |||
56 | static inline struct syscon_gpio_priv *to_syscon_gpio(struct gpio_chip *chip) | ||
57 | { | ||
58 | return container_of(chip, struct syscon_gpio_priv, chip); | ||
59 | } | ||
60 | |||
61 | static int syscon_gpio_get(struct gpio_chip *chip, unsigned offset) | ||
62 | { | ||
63 | struct syscon_gpio_priv *priv = to_syscon_gpio(chip); | ||
64 | unsigned int val, offs = priv->data->dat_bit_offset + offset; | ||
65 | int ret; | ||
66 | |||
67 | ret = regmap_read(priv->syscon, | ||
68 | (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, &val); | ||
69 | if (ret) | ||
70 | return ret; | ||
71 | |||
72 | return !!(val & BIT(offs % SYSCON_REG_BITS)); | ||
73 | } | ||
74 | |||
75 | static void syscon_gpio_set(struct gpio_chip *chip, unsigned offset, int val) | ||
76 | { | ||
77 | struct syscon_gpio_priv *priv = to_syscon_gpio(chip); | ||
78 | unsigned int offs = priv->data->dat_bit_offset + offset; | ||
79 | |||
80 | regmap_update_bits(priv->syscon, | ||
81 | (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, | ||
82 | BIT(offs % SYSCON_REG_BITS), | ||
83 | val ? BIT(offs % SYSCON_REG_BITS) : 0); | ||
84 | } | ||
85 | |||
86 | static int syscon_gpio_dir_in(struct gpio_chip *chip, unsigned offset) | ||
87 | { | ||
88 | struct syscon_gpio_priv *priv = to_syscon_gpio(chip); | ||
89 | |||
90 | if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { | ||
91 | unsigned int offs = priv->data->dir_bit_offset + offset; | ||
92 | |||
93 | regmap_update_bits(priv->syscon, | ||
94 | (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, | ||
95 | BIT(offs % SYSCON_REG_BITS), 0); | ||
96 | } | ||
97 | |||
98 | return 0; | ||
99 | } | ||
100 | |||
101 | static int syscon_gpio_dir_out(struct gpio_chip *chip, unsigned offset, int val) | ||
102 | { | ||
103 | struct syscon_gpio_priv *priv = to_syscon_gpio(chip); | ||
104 | |||
105 | if (priv->data->flags & GPIO_SYSCON_FEAT_DIR) { | ||
106 | unsigned int offs = priv->data->dir_bit_offset + offset; | ||
107 | |||
108 | regmap_update_bits(priv->syscon, | ||
109 | (offs / SYSCON_REG_BITS) * SYSCON_REG_SIZE, | ||
110 | BIT(offs % SYSCON_REG_BITS), | ||
111 | BIT(offs % SYSCON_REG_BITS)); | ||
112 | } | ||
113 | |||
114 | syscon_gpio_set(chip, offset, val); | ||
115 | |||
116 | return 0; | ||
117 | } | ||
118 | |||
119 | static const struct syscon_gpio_data clps711x_mctrl_gpio = { | ||
120 | /* ARM CLPS711X SYSFLG1 Bits 8-10 */ | ||
121 | .compatible = "cirrus,clps711x-syscon1", | ||
122 | .flags = GPIO_SYSCON_FEAT_IN, | ||
123 | .bit_count = 3, | ||
124 | .dat_bit_offset = 0x40 * 8 + 8, | ||
125 | }; | ||
126 | |||
127 | static const struct of_device_id syscon_gpio_ids[] = { | ||
128 | { | ||
129 | .compatible = "cirrus,clps711x-mctrl-gpio", | ||
130 | .data = &clps711x_mctrl_gpio, | ||
131 | }, | ||
132 | { } | ||
133 | }; | ||
134 | MODULE_DEVICE_TABLE(of, syscon_gpio_ids); | ||
135 | |||
136 | static int syscon_gpio_probe(struct platform_device *pdev) | ||
137 | { | ||
138 | struct device *dev = &pdev->dev; | ||
139 | const struct of_device_id *of_id = of_match_device(syscon_gpio_ids, dev); | ||
140 | struct syscon_gpio_priv *priv; | ||
141 | |||
142 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | ||
143 | if (!priv) | ||
144 | return -ENOMEM; | ||
145 | |||
146 | priv->data = of_id->data; | ||
147 | |||
148 | priv->syscon = | ||
149 | syscon_regmap_lookup_by_compatible(priv->data->compatible); | ||
150 | if (IS_ERR(priv->syscon)) | ||
151 | return PTR_ERR(priv->syscon); | ||
152 | |||
153 | priv->chip.dev = dev; | ||
154 | priv->chip.owner = THIS_MODULE; | ||
155 | priv->chip.label = dev_name(dev); | ||
156 | priv->chip.base = -1; | ||
157 | priv->chip.ngpio = priv->data->bit_count; | ||
158 | priv->chip.get = syscon_gpio_get; | ||
159 | if (priv->data->flags & GPIO_SYSCON_FEAT_IN) | ||
160 | priv->chip.direction_input = syscon_gpio_dir_in; | ||
161 | if (priv->data->flags & GPIO_SYSCON_FEAT_OUT) { | ||
162 | priv->chip.set = syscon_gpio_set; | ||
163 | priv->chip.direction_output = syscon_gpio_dir_out; | ||
164 | } | ||
165 | |||
166 | platform_set_drvdata(pdev, priv); | ||
167 | |||
168 | return gpiochip_add(&priv->chip); | ||
169 | } | ||
170 | |||
171 | static int syscon_gpio_remove(struct platform_device *pdev) | ||
172 | { | ||
173 | struct syscon_gpio_priv *priv = platform_get_drvdata(pdev); | ||
174 | |||
175 | return gpiochip_remove(&priv->chip); | ||
176 | } | ||
177 | |||
178 | static struct platform_driver syscon_gpio_driver = { | ||
179 | .driver = { | ||
180 | .name = "gpio-syscon", | ||
181 | .owner = THIS_MODULE, | ||
182 | .of_match_table = syscon_gpio_ids, | ||
183 | }, | ||
184 | .probe = syscon_gpio_probe, | ||
185 | .remove = syscon_gpio_remove, | ||
186 | }; | ||
187 | module_platform_driver(syscon_gpio_driver); | ||
188 | |||
189 | MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); | ||
190 | MODULE_DESCRIPTION("SYSCON GPIO driver"); | ||
191 | MODULE_LICENSE("GPL"); | ||