Commit | Line | Data |
---|---|---|
3b0213d5 GF |
1 | /* |
2 | * Copyright (C) 2015 Broadcom Corporation | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License as | |
6 | * published by the Free Software Foundation version 2. | |
7 | * | |
8 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | |
9 | * kind, whether express or implied; without even the implied warranty | |
10 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | * GNU General Public License for more details. | |
12 | */ | |
13 | ||
14 | #include <linux/bitops.h> | |
15 | #include <linux/gpio/driver.h> | |
16 | #include <linux/of_device.h> | |
17 | #include <linux/of_irq.h> | |
18 | #include <linux/module.h> | |
19 | #include <linux/basic_mmio_gpio.h> | |
20 | ||
21 | #define GIO_BANK_SIZE 0x20 | |
22 | #define GIO_ODEN(bank) (((bank) * GIO_BANK_SIZE) + 0x00) | |
23 | #define GIO_DATA(bank) (((bank) * GIO_BANK_SIZE) + 0x04) | |
24 | #define GIO_IODIR(bank) (((bank) * GIO_BANK_SIZE) + 0x08) | |
25 | #define GIO_EC(bank) (((bank) * GIO_BANK_SIZE) + 0x0c) | |
26 | #define GIO_EI(bank) (((bank) * GIO_BANK_SIZE) + 0x10) | |
27 | #define GIO_MASK(bank) (((bank) * GIO_BANK_SIZE) + 0x14) | |
28 | #define GIO_LEVEL(bank) (((bank) * GIO_BANK_SIZE) + 0x18) | |
29 | #define GIO_STAT(bank) (((bank) * GIO_BANK_SIZE) + 0x1c) | |
30 | ||
31 | struct brcmstb_gpio_bank { | |
32 | struct list_head node; | |
33 | int id; | |
34 | struct bgpio_chip bgc; | |
35 | struct brcmstb_gpio_priv *parent_priv; | |
36 | u32 width; | |
37 | }; | |
38 | ||
39 | struct brcmstb_gpio_priv { | |
40 | struct list_head bank_list; | |
41 | void __iomem *reg_base; | |
42 | int num_banks; | |
43 | struct platform_device *pdev; | |
44 | int gpio_base; | |
45 | }; | |
46 | ||
47 | #define MAX_GPIO_PER_BANK 32 | |
48 | #define GPIO_BANK(gpio) ((gpio) >> 5) | |
49 | /* assumes MAX_GPIO_PER_BANK is a multiple of 2 */ | |
50 | #define GPIO_BIT(gpio) ((gpio) & (MAX_GPIO_PER_BANK - 1)) | |
51 | ||
52 | static inline struct brcmstb_gpio_bank * | |
53 | brcmstb_gpio_gc_to_bank(struct gpio_chip *gc) | |
54 | { | |
55 | struct bgpio_chip *bgc = to_bgpio_chip(gc); | |
56 | return container_of(bgc, struct brcmstb_gpio_bank, bgc); | |
57 | } | |
58 | ||
59 | static inline struct brcmstb_gpio_priv * | |
60 | brcmstb_gpio_gc_to_priv(struct gpio_chip *gc) | |
61 | { | |
62 | struct brcmstb_gpio_bank *bank = brcmstb_gpio_gc_to_bank(gc); | |
63 | return bank->parent_priv; | |
64 | } | |
65 | ||
66 | /* Make sure that the number of banks matches up between properties */ | |
67 | static int brcmstb_gpio_sanity_check_banks(struct device *dev, | |
68 | struct device_node *np, struct resource *res) | |
69 | { | |
70 | int res_num_banks = resource_size(res) / GIO_BANK_SIZE; | |
71 | int num_banks = | |
72 | of_property_count_u32_elems(np, "brcm,gpio-bank-widths"); | |
73 | ||
74 | if (res_num_banks != num_banks) { | |
75 | dev_err(dev, "Mismatch in banks: res had %d, bank-widths had %d\n", | |
76 | res_num_banks, num_banks); | |
77 | return -EINVAL; | |
78 | } else { | |
79 | return 0; | |
80 | } | |
81 | } | |
82 | ||
83 | static int brcmstb_gpio_remove(struct platform_device *pdev) | |
84 | { | |
85 | struct brcmstb_gpio_priv *priv = platform_get_drvdata(pdev); | |
86 | struct list_head *pos; | |
87 | struct brcmstb_gpio_bank *bank; | |
88 | int ret = 0; | |
89 | ||
2252607d GF |
90 | if (!priv) { |
91 | dev_err(&pdev->dev, "called %s without drvdata!\n", __func__); | |
92 | return -EFAULT; | |
93 | } | |
94 | ||
95 | /* | |
96 | * You can lose return values below, but we report all errors, and it's | |
97 | * more important to actually perform all of the steps. | |
98 | */ | |
3b0213d5 GF |
99 | list_for_each(pos, &priv->bank_list) { |
100 | bank = list_entry(pos, struct brcmstb_gpio_bank, node); | |
101 | ret = bgpio_remove(&bank->bgc); | |
102 | if (ret) | |
103 | dev_err(&pdev->dev, "gpiochip_remove fail in cleanup"); | |
104 | } | |
105 | return ret; | |
106 | } | |
107 | ||
108 | static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, | |
109 | const struct of_phandle_args *gpiospec, u32 *flags) | |
110 | { | |
111 | struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); | |
112 | struct brcmstb_gpio_bank *bank = brcmstb_gpio_gc_to_bank(gc); | |
113 | int offset; | |
114 | ||
115 | if (gc->of_gpio_n_cells != 2) { | |
116 | WARN_ON(1); | |
117 | return -EINVAL; | |
118 | } | |
119 | ||
120 | if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells)) | |
121 | return -EINVAL; | |
122 | ||
123 | offset = gpiospec->args[0] - (gc->base - priv->gpio_base); | |
124 | if (offset >= gc->ngpio) | |
125 | return -EINVAL; | |
126 | ||
127 | if (unlikely(offset >= bank->width)) { | |
128 | dev_warn_ratelimited(&priv->pdev->dev, | |
129 | "Received request for invalid GPIO offset %d\n", | |
130 | gpiospec->args[0]); | |
131 | } | |
132 | ||
133 | if (flags) | |
134 | *flags = gpiospec->args[1]; | |
135 | ||
136 | return offset; | |
137 | } | |
138 | ||
139 | static int brcmstb_gpio_probe(struct platform_device *pdev) | |
140 | { | |
141 | struct device *dev = &pdev->dev; | |
142 | struct device_node *np = dev->of_node; | |
143 | void __iomem *reg_base; | |
144 | struct brcmstb_gpio_priv *priv; | |
145 | struct resource *res; | |
146 | struct property *prop; | |
147 | const __be32 *p; | |
148 | u32 bank_width; | |
149 | int err; | |
150 | static int gpio_base; | |
151 | ||
152 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
153 | if (!priv) | |
154 | return -ENOMEM; | |
2252607d GF |
155 | platform_set_drvdata(pdev, priv); |
156 | INIT_LIST_HEAD(&priv->bank_list); | |
3b0213d5 GF |
157 | |
158 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
159 | reg_base = devm_ioremap_resource(dev, res); | |
160 | if (IS_ERR(reg_base)) | |
161 | return PTR_ERR(reg_base); | |
162 | ||
163 | priv->gpio_base = gpio_base; | |
164 | priv->reg_base = reg_base; | |
165 | priv->pdev = pdev; | |
166 | ||
3b0213d5 GF |
167 | if (brcmstb_gpio_sanity_check_banks(dev, np, res)) |
168 | return -EINVAL; | |
169 | ||
170 | of_property_for_each_u32(np, "brcm,gpio-bank-widths", prop, p, | |
171 | bank_width) { | |
172 | struct brcmstb_gpio_bank *bank; | |
173 | struct bgpio_chip *bgc; | |
174 | struct gpio_chip *gc; | |
175 | ||
176 | bank = devm_kzalloc(dev, sizeof(*bank), GFP_KERNEL); | |
177 | if (!bank) { | |
178 | err = -ENOMEM; | |
179 | goto fail; | |
180 | } | |
181 | ||
182 | bank->parent_priv = priv; | |
183 | bank->id = priv->num_banks; | |
184 | if (bank_width <= 0 || bank_width > MAX_GPIO_PER_BANK) { | |
185 | dev_err(dev, "Invalid bank width %d\n", bank_width); | |
186 | goto fail; | |
187 | } else { | |
188 | bank->width = bank_width; | |
189 | } | |
190 | ||
191 | /* | |
192 | * Regs are 4 bytes wide, have data reg, no set/clear regs, | |
193 | * and direction bits have 0 = output and 1 = input | |
194 | */ | |
195 | bgc = &bank->bgc; | |
196 | err = bgpio_init(bgc, dev, 4, | |
197 | reg_base + GIO_DATA(bank->id), | |
198 | NULL, NULL, NULL, | |
199 | reg_base + GIO_IODIR(bank->id), 0); | |
200 | if (err) { | |
201 | dev_err(dev, "bgpio_init() failed\n"); | |
202 | goto fail; | |
203 | } | |
204 | ||
205 | gc = &bgc->gc; | |
206 | gc->of_node = np; | |
207 | gc->owner = THIS_MODULE; | |
208 | gc->label = np->full_name; | |
209 | gc->base = gpio_base; | |
210 | gc->of_gpio_n_cells = 2; | |
211 | gc->of_xlate = brcmstb_gpio_of_xlate; | |
212 | /* not all ngpio lines are valid, will use bank width later */ | |
213 | gc->ngpio = MAX_GPIO_PER_BANK; | |
214 | ||
215 | err = gpiochip_add(gc); | |
216 | if (err) { | |
217 | dev_err(dev, "Could not add gpiochip for bank %d\n", | |
218 | bank->id); | |
219 | goto fail; | |
220 | } | |
221 | gpio_base += gc->ngpio; | |
222 | dev_dbg(dev, "bank=%d, base=%d, ngpio=%d, width=%d\n", bank->id, | |
223 | gc->base, gc->ngpio, bank->width); | |
224 | ||
225 | /* Everything looks good, so add bank to list */ | |
226 | list_add(&bank->node, &priv->bank_list); | |
227 | ||
228 | priv->num_banks++; | |
229 | } | |
230 | ||
231 | dev_info(dev, "Registered %d banks (GPIO(s): %d-%d)\n", | |
232 | priv->num_banks, priv->gpio_base, gpio_base - 1); | |
233 | ||
3b0213d5 GF |
234 | return 0; |
235 | ||
236 | fail: | |
237 | (void) brcmstb_gpio_remove(pdev); | |
238 | return err; | |
239 | } | |
240 | ||
241 | static const struct of_device_id brcmstb_gpio_of_match[] = { | |
242 | { .compatible = "brcm,brcmstb-gpio" }, | |
243 | {}, | |
244 | }; | |
245 | ||
246 | MODULE_DEVICE_TABLE(of, brcmstb_gpio_of_match); | |
247 | ||
248 | static struct platform_driver brcmstb_gpio_driver = { | |
249 | .driver = { | |
250 | .name = "brcmstb-gpio", | |
251 | .of_match_table = brcmstb_gpio_of_match, | |
252 | }, | |
253 | .probe = brcmstb_gpio_probe, | |
254 | .remove = brcmstb_gpio_remove, | |
255 | }; | |
256 | module_platform_driver(brcmstb_gpio_driver); | |
257 | ||
258 | MODULE_AUTHOR("Gregory Fong"); | |
259 | MODULE_DESCRIPTION("Driver for Broadcom BRCMSTB SoC UPG GPIO"); | |
260 | MODULE_LICENSE("GPL v2"); |