Commit | Line | Data |
---|---|---|
cc7c2bb1 AD |
1 | /* |
2 | * TI SYSCON regmap reset driver | |
3 | * | |
4 | * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/ | |
5 | * Andrew F. Davis <afd@ti.com> | |
6 | * Suman Anna <afd@ti.com> | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or modify | |
9 | * it under the terms of the GNU General Public License version 2 as | |
10 | * published by the Free Software Foundation. | |
11 | * | |
12 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any | |
13 | * kind, whether express or implied; without even the implied warranty | |
14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | */ | |
17 | ||
18 | #include <linux/mfd/syscon.h> | |
19 | #include <linux/module.h> | |
20 | #include <linux/of.h> | |
21 | #include <linux/platform_device.h> | |
22 | #include <linux/regmap.h> | |
23 | #include <linux/reset-controller.h> | |
24 | ||
25 | #include <dt-bindings/reset/ti-syscon.h> | |
26 | ||
27 | /** | |
28 | * struct ti_syscon_reset_control - reset control structure | |
29 | * @assert_offset: reset assert control register offset from syscon base | |
30 | * @assert_bit: reset assert bit in the reset assert control register | |
31 | * @deassert_offset: reset deassert control register offset from syscon base | |
32 | * @deassert_bit: reset deassert bit in the reset deassert control register | |
33 | * @status_offset: reset status register offset from syscon base | |
34 | * @status_bit: reset status bit in the reset status register | |
35 | * @flags: reset flag indicating how the (de)assert and status are handled | |
36 | */ | |
37 | struct ti_syscon_reset_control { | |
38 | unsigned int assert_offset; | |
39 | unsigned int assert_bit; | |
40 | unsigned int deassert_offset; | |
41 | unsigned int deassert_bit; | |
42 | unsigned int status_offset; | |
43 | unsigned int status_bit; | |
44 | u32 flags; | |
45 | }; | |
46 | ||
47 | /** | |
48 | * struct ti_syscon_reset_data - reset controller information structure | |
49 | * @rcdev: reset controller entity | |
50 | * @regmap: regmap handle containing the memory-mapped reset registers | |
51 | * @controls: array of reset controls | |
52 | * @nr_controls: number of controls in control array | |
53 | */ | |
54 | struct ti_syscon_reset_data { | |
55 | struct reset_controller_dev rcdev; | |
56 | struct regmap *regmap; | |
57 | struct ti_syscon_reset_control *controls; | |
58 | unsigned int nr_controls; | |
59 | }; | |
60 | ||
61 | #define to_ti_syscon_reset_data(rcdev) \ | |
62 | container_of(rcdev, struct ti_syscon_reset_data, rcdev) | |
63 | ||
64 | /** | |
65 | * ti_syscon_reset_assert() - assert device reset | |
66 | * @rcdev: reset controller entity | |
67 | * @id: ID of the reset to be asserted | |
68 | * | |
69 | * This function implements the reset driver op to assert a device's reset. | |
70 | * This asserts the reset in a manner prescribed by the reset flags. | |
71 | * | |
72 | * Return: 0 for successful request, else a corresponding error value | |
73 | */ | |
74 | static int ti_syscon_reset_assert(struct reset_controller_dev *rcdev, | |
75 | unsigned long id) | |
76 | { | |
77 | struct ti_syscon_reset_data *data = to_ti_syscon_reset_data(rcdev); | |
78 | struct ti_syscon_reset_control *control; | |
79 | unsigned int mask, value; | |
80 | ||
81 | if (id >= data->nr_controls) | |
82 | return -EINVAL; | |
83 | ||
84 | control = &data->controls[id]; | |
85 | ||
86 | if (control->flags & ASSERT_NONE) | |
87 | return -ENOTSUPP; /* assert not supported for this reset */ | |
88 | ||
89 | mask = BIT(control->assert_bit); | |
90 | value = (control->flags & ASSERT_SET) ? mask : 0x0; | |
91 | ||
92 | return regmap_update_bits(data->regmap, control->assert_offset, mask, value); | |
93 | } | |
94 | ||
95 | /** | |
96 | * ti_syscon_reset_deassert() - deassert device reset | |
97 | * @rcdev: reset controller entity | |
98 | * @id: ID of reset to be deasserted | |
99 | * | |
100 | * This function implements the reset driver op to deassert a device's reset. | |
101 | * This deasserts the reset in a manner prescribed by the reset flags. | |
102 | * | |
103 | * Return: 0 for successful request, else a corresponding error value | |
104 | */ | |
105 | static int ti_syscon_reset_deassert(struct reset_controller_dev *rcdev, | |
106 | unsigned long id) | |
107 | { | |
108 | struct ti_syscon_reset_data *data = to_ti_syscon_reset_data(rcdev); | |
109 | struct ti_syscon_reset_control *control; | |
110 | unsigned int mask, value; | |
111 | ||
112 | if (id >= data->nr_controls) | |
113 | return -EINVAL; | |
114 | ||
115 | control = &data->controls[id]; | |
116 | ||
117 | if (control->flags & DEASSERT_NONE) | |
118 | return -ENOTSUPP; /* deassert not supported for this reset */ | |
119 | ||
120 | mask = BIT(control->deassert_bit); | |
121 | value = (control->flags & DEASSERT_SET) ? mask : 0x0; | |
122 | ||
123 | return regmap_update_bits(data->regmap, control->deassert_offset, mask, value); | |
124 | } | |
125 | ||
126 | /** | |
127 | * ti_syscon_reset_status() - check device reset status | |
128 | * @rcdev: reset controller entity | |
129 | * @id: ID of the reset for which the status is being requested | |
130 | * | |
131 | * This function implements the reset driver op to return the status of a | |
132 | * device's reset. | |
133 | * | |
134 | * Return: 0 if reset is deasserted, true if reset is asserted, else a | |
135 | * corresponding error value | |
136 | */ | |
137 | static int ti_syscon_reset_status(struct reset_controller_dev *rcdev, | |
138 | unsigned long id) | |
139 | { | |
140 | struct ti_syscon_reset_data *data = to_ti_syscon_reset_data(rcdev); | |
141 | struct ti_syscon_reset_control *control; | |
142 | unsigned int reset_state; | |
143 | int ret; | |
144 | ||
145 | if (id >= data->nr_controls) | |
146 | return -EINVAL; | |
147 | ||
148 | control = &data->controls[id]; | |
149 | ||
150 | if (control->flags & STATUS_NONE) | |
151 | return -ENOTSUPP; /* status not supported for this reset */ | |
152 | ||
153 | ret = regmap_read(data->regmap, control->status_offset, &reset_state); | |
154 | if (ret) | |
155 | return ret; | |
156 | ||
5987b4bf JX |
157 | return !(reset_state & BIT(control->status_bit)) == |
158 | !(control->flags & STATUS_SET); | |
cc7c2bb1 AD |
159 | } |
160 | ||
10132588 | 161 | static const struct reset_control_ops ti_syscon_reset_ops = { |
cc7c2bb1 AD |
162 | .assert = ti_syscon_reset_assert, |
163 | .deassert = ti_syscon_reset_deassert, | |
164 | .status = ti_syscon_reset_status, | |
165 | }; | |
166 | ||
167 | static int ti_syscon_reset_probe(struct platform_device *pdev) | |
168 | { | |
169 | struct device *dev = &pdev->dev; | |
170 | struct device_node *np = dev->of_node; | |
171 | struct ti_syscon_reset_data *data; | |
172 | struct regmap *regmap; | |
173 | const __be32 *list; | |
174 | struct ti_syscon_reset_control *controls; | |
175 | int size, nr_controls, i; | |
176 | ||
177 | data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | |
178 | if (!data) | |
179 | return -ENOMEM; | |
180 | ||
181 | regmap = syscon_node_to_regmap(np->parent); | |
182 | if (IS_ERR(regmap)) | |
183 | return PTR_ERR(regmap); | |
184 | ||
185 | list = of_get_property(np, "ti,reset-bits", &size); | |
186 | if (!list || (size / sizeof(*list)) % 7 != 0) { | |
187 | dev_err(dev, "invalid DT reset description\n"); | |
188 | return -EINVAL; | |
189 | } | |
190 | ||
191 | nr_controls = (size / sizeof(*list)) / 7; | |
192 | controls = devm_kzalloc(dev, nr_controls * sizeof(*controls), GFP_KERNEL); | |
193 | if (!controls) | |
194 | return -ENOMEM; | |
195 | ||
196 | for (i = 0; i < nr_controls; i++) { | |
197 | controls[i].assert_offset = be32_to_cpup(list++); | |
198 | controls[i].assert_bit = be32_to_cpup(list++); | |
199 | controls[i].deassert_offset = be32_to_cpup(list++); | |
200 | controls[i].deassert_bit = be32_to_cpup(list++); | |
201 | controls[i].status_offset = be32_to_cpup(list++); | |
202 | controls[i].status_bit = be32_to_cpup(list++); | |
203 | controls[i].flags = be32_to_cpup(list++); | |
204 | } | |
205 | ||
206 | data->rcdev.ops = &ti_syscon_reset_ops; | |
207 | data->rcdev.owner = THIS_MODULE; | |
208 | data->rcdev.of_node = np; | |
209 | data->rcdev.nr_resets = nr_controls; | |
210 | data->regmap = regmap; | |
211 | data->controls = controls; | |
212 | data->nr_controls = nr_controls; | |
213 | ||
214 | platform_set_drvdata(pdev, data); | |
215 | ||
216 | return devm_reset_controller_register(dev, &data->rcdev); | |
217 | } | |
218 | ||
219 | static const struct of_device_id ti_syscon_reset_of_match[] = { | |
220 | { .compatible = "ti,syscon-reset", }, | |
221 | { /* sentinel */ }, | |
222 | }; | |
223 | MODULE_DEVICE_TABLE(of, ti_syscon_reset_of_match); | |
224 | ||
225 | static struct platform_driver ti_syscon_reset_driver = { | |
226 | .probe = ti_syscon_reset_probe, | |
227 | .driver = { | |
228 | .name = "ti-syscon-reset", | |
229 | .of_match_table = ti_syscon_reset_of_match, | |
230 | }, | |
231 | }; | |
232 | module_platform_driver(ti_syscon_reset_driver); | |
233 | ||
234 | MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>"); | |
235 | MODULE_AUTHOR("Suman Anna <s-anna@ti.com>"); | |
236 | MODULE_DESCRIPTION("TI SYSCON Regmap Reset Driver"); | |
237 | MODULE_LICENSE("GPL v2"); |