]>
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 | ||
90 | list_for_each(pos, &priv->bank_list) { | |
91 | bank = list_entry(pos, struct brcmstb_gpio_bank, node); | |
92 | ret = bgpio_remove(&bank->bgc); | |
93 | if (ret) | |
94 | dev_err(&pdev->dev, "gpiochip_remove fail in cleanup"); | |
95 | } | |
96 | return ret; | |
97 | } | |
98 | ||
99 | static int brcmstb_gpio_of_xlate(struct gpio_chip *gc, | |
100 | const struct of_phandle_args *gpiospec, u32 *flags) | |
101 | { | |
102 | struct brcmstb_gpio_priv *priv = brcmstb_gpio_gc_to_priv(gc); | |
103 | struct brcmstb_gpio_bank *bank = brcmstb_gpio_gc_to_bank(gc); | |
104 | int offset; | |
105 | ||
106 | if (gc->of_gpio_n_cells != 2) { | |
107 | WARN_ON(1); | |
108 | return -EINVAL; | |
109 | } | |
110 | ||
111 | if (WARN_ON(gpiospec->args_count < gc->of_gpio_n_cells)) | |
112 | return -EINVAL; | |
113 | ||
114 | offset = gpiospec->args[0] - (gc->base - priv->gpio_base); | |
115 | if (offset >= gc->ngpio) | |
116 | return -EINVAL; | |
117 | ||
118 | if (unlikely(offset >= bank->width)) { | |
119 | dev_warn_ratelimited(&priv->pdev->dev, | |
120 | "Received request for invalid GPIO offset %d\n", | |
121 | gpiospec->args[0]); | |
122 | } | |
123 | ||
124 | if (flags) | |
125 | *flags = gpiospec->args[1]; | |
126 | ||
127 | return offset; | |
128 | } | |
129 | ||
130 | static int brcmstb_gpio_probe(struct platform_device *pdev) | |
131 | { | |
132 | struct device *dev = &pdev->dev; | |
133 | struct device_node *np = dev->of_node; | |
134 | void __iomem *reg_base; | |
135 | struct brcmstb_gpio_priv *priv; | |
136 | struct resource *res; | |
137 | struct property *prop; | |
138 | const __be32 *p; | |
139 | u32 bank_width; | |
140 | int err; | |
141 | static int gpio_base; | |
142 | ||
143 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); | |
144 | if (!priv) | |
145 | return -ENOMEM; | |
146 | ||
147 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
148 | reg_base = devm_ioremap_resource(dev, res); | |
149 | if (IS_ERR(reg_base)) | |
150 | return PTR_ERR(reg_base); | |
151 | ||
152 | priv->gpio_base = gpio_base; | |
153 | priv->reg_base = reg_base; | |
154 | priv->pdev = pdev; | |
155 | ||
156 | INIT_LIST_HEAD(&priv->bank_list); | |
157 | if (brcmstb_gpio_sanity_check_banks(dev, np, res)) | |
158 | return -EINVAL; | |
159 | ||
160 | of_property_for_each_u32(np, "brcm,gpio-bank-widths", prop, p, | |
161 | bank_width) { | |
162 | struct brcmstb_gpio_bank *bank; | |
163 | struct bgpio_chip *bgc; | |
164 | struct gpio_chip *gc; | |
165 | ||
166 | bank = devm_kzalloc(dev, sizeof(*bank), GFP_KERNEL); | |
167 | if (!bank) { | |
168 | err = -ENOMEM; | |
169 | goto fail; | |
170 | } | |
171 | ||
172 | bank->parent_priv = priv; | |
173 | bank->id = priv->num_banks; | |
174 | if (bank_width <= 0 || bank_width > MAX_GPIO_PER_BANK) { | |
175 | dev_err(dev, "Invalid bank width %d\n", bank_width); | |
176 | goto fail; | |
177 | } else { | |
178 | bank->width = bank_width; | |
179 | } | |
180 | ||
181 | /* | |
182 | * Regs are 4 bytes wide, have data reg, no set/clear regs, | |
183 | * and direction bits have 0 = output and 1 = input | |
184 | */ | |
185 | bgc = &bank->bgc; | |
186 | err = bgpio_init(bgc, dev, 4, | |
187 | reg_base + GIO_DATA(bank->id), | |
188 | NULL, NULL, NULL, | |
189 | reg_base + GIO_IODIR(bank->id), 0); | |
190 | if (err) { | |
191 | dev_err(dev, "bgpio_init() failed\n"); | |
192 | goto fail; | |
193 | } | |
194 | ||
195 | gc = &bgc->gc; | |
196 | gc->of_node = np; | |
197 | gc->owner = THIS_MODULE; | |
198 | gc->label = np->full_name; | |
199 | gc->base = gpio_base; | |
200 | gc->of_gpio_n_cells = 2; | |
201 | gc->of_xlate = brcmstb_gpio_of_xlate; | |
202 | /* not all ngpio lines are valid, will use bank width later */ | |
203 | gc->ngpio = MAX_GPIO_PER_BANK; | |
204 | ||
205 | err = gpiochip_add(gc); | |
206 | if (err) { | |
207 | dev_err(dev, "Could not add gpiochip for bank %d\n", | |
208 | bank->id); | |
209 | goto fail; | |
210 | } | |
211 | gpio_base += gc->ngpio; | |
212 | dev_dbg(dev, "bank=%d, base=%d, ngpio=%d, width=%d\n", bank->id, | |
213 | gc->base, gc->ngpio, bank->width); | |
214 | ||
215 | /* Everything looks good, so add bank to list */ | |
216 | list_add(&bank->node, &priv->bank_list); | |
217 | ||
218 | priv->num_banks++; | |
219 | } | |
220 | ||
221 | dev_info(dev, "Registered %d banks (GPIO(s): %d-%d)\n", | |
222 | priv->num_banks, priv->gpio_base, gpio_base - 1); | |
223 | ||
224 | platform_set_drvdata(pdev, priv); | |
225 | ||
226 | return 0; | |
227 | ||
228 | fail: | |
229 | (void) brcmstb_gpio_remove(pdev); | |
230 | return err; | |
231 | } | |
232 | ||
233 | static const struct of_device_id brcmstb_gpio_of_match[] = { | |
234 | { .compatible = "brcm,brcmstb-gpio" }, | |
235 | {}, | |
236 | }; | |
237 | ||
238 | MODULE_DEVICE_TABLE(of, brcmstb_gpio_of_match); | |
239 | ||
240 | static struct platform_driver brcmstb_gpio_driver = { | |
241 | .driver = { | |
242 | .name = "brcmstb-gpio", | |
243 | .of_match_table = brcmstb_gpio_of_match, | |
244 | }, | |
245 | .probe = brcmstb_gpio_probe, | |
246 | .remove = brcmstb_gpio_remove, | |
247 | }; | |
248 | module_platform_driver(brcmstb_gpio_driver); | |
249 | ||
250 | MODULE_AUTHOR("Gregory Fong"); | |
251 | MODULE_DESCRIPTION("Driver for Broadcom BRCMSTB SoC UPG GPIO"); | |
252 | MODULE_LICENSE("GPL v2"); |