1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) ST-Ericsson SA 2011 |
4 | * |
5 | * Author: Lee Jones <lee.jones@linaro.org> for ST-Ericsson. |
6 | */ |
7 | |
8 | #include <linux/sysfs.h> |
9 | #include <linux/init.h> |
10 | #include <linux/of.h> |
11 | #include <linux/stat.h> |
12 | #include <linux/slab.h> |
13 | #include <linux/idr.h> |
14 | #include <linux/spinlock.h> |
15 | #include <linux/sys_soc.h> |
16 | #include <linux/err.h> |
17 | #include <linux/glob.h> |
18 | |
19 | static DEFINE_IDA(soc_ida); |
20 | |
21 | /* Prototype to allow declarations of DEVICE_ATTR(<foo>) before soc_info_show */ |
22 | static ssize_t soc_info_show(struct device *dev, struct device_attribute *attr, |
23 | char *buf); |
24 | |
25 | struct soc_device { |
26 | struct device dev; |
27 | struct soc_device_attribute *attr; |
28 | int soc_dev_num; |
29 | }; |
30 | |
31 | static struct bus_type soc_bus_type = { |
32 | .name = "soc" , |
33 | }; |
34 | static bool soc_bus_registered; |
35 | |
36 | static DEVICE_ATTR(machine, 0444, soc_info_show, NULL); |
37 | static DEVICE_ATTR(family, 0444, soc_info_show, NULL); |
38 | static DEVICE_ATTR(serial_number, 0444, soc_info_show, NULL); |
39 | static DEVICE_ATTR(soc_id, 0444, soc_info_show, NULL); |
40 | static DEVICE_ATTR(revision, 0444, soc_info_show, NULL); |
41 | |
42 | struct device *soc_device_to_device(struct soc_device *soc_dev) |
43 | { |
44 | return &soc_dev->dev; |
45 | } |
46 | |
47 | static umode_t soc_attribute_mode(struct kobject *kobj, |
48 | struct attribute *attr, |
49 | int index) |
50 | { |
51 | struct device *dev = kobj_to_dev(kobj); |
52 | struct soc_device *soc_dev = container_of(dev, struct soc_device, dev); |
53 | |
54 | if ((attr == &dev_attr_machine.attr) && soc_dev->attr->machine) |
55 | return attr->mode; |
56 | if ((attr == &dev_attr_family.attr) && soc_dev->attr->family) |
57 | return attr->mode; |
58 | if ((attr == &dev_attr_revision.attr) && soc_dev->attr->revision) |
59 | return attr->mode; |
60 | if ((attr == &dev_attr_serial_number.attr) && soc_dev->attr->serial_number) |
61 | return attr->mode; |
62 | if ((attr == &dev_attr_soc_id.attr) && soc_dev->attr->soc_id) |
63 | return attr->mode; |
64 | |
65 | /* Unknown or unfilled attribute */ |
66 | return 0; |
67 | } |
68 | |
69 | static ssize_t soc_info_show(struct device *dev, struct device_attribute *attr, |
70 | char *buf) |
71 | { |
72 | struct soc_device *soc_dev = container_of(dev, struct soc_device, dev); |
73 | const char *output; |
74 | |
75 | if (attr == &dev_attr_machine) |
76 | output = soc_dev->attr->machine; |
77 | else if (attr == &dev_attr_family) |
78 | output = soc_dev->attr->family; |
79 | else if (attr == &dev_attr_revision) |
80 | output = soc_dev->attr->revision; |
81 | else if (attr == &dev_attr_serial_number) |
82 | output = soc_dev->attr->serial_number; |
83 | else if (attr == &dev_attr_soc_id) |
84 | output = soc_dev->attr->soc_id; |
85 | else |
86 | return -EINVAL; |
87 | |
88 | return sysfs_emit(buf, fmt: "%s\n" , output); |
89 | } |
90 | |
91 | static struct attribute *soc_attr[] = { |
92 | &dev_attr_machine.attr, |
93 | &dev_attr_family.attr, |
94 | &dev_attr_serial_number.attr, |
95 | &dev_attr_soc_id.attr, |
96 | &dev_attr_revision.attr, |
97 | NULL, |
98 | }; |
99 | |
100 | static const struct attribute_group soc_attr_group = { |
101 | .attrs = soc_attr, |
102 | .is_visible = soc_attribute_mode, |
103 | }; |
104 | |
105 | static void soc_release(struct device *dev) |
106 | { |
107 | struct soc_device *soc_dev = container_of(dev, struct soc_device, dev); |
108 | |
109 | ida_simple_remove(&soc_ida, soc_dev->soc_dev_num); |
110 | kfree(objp: soc_dev->dev.groups); |
111 | kfree(objp: soc_dev); |
112 | } |
113 | |
114 | static void soc_device_get_machine(struct soc_device_attribute *soc_dev_attr) |
115 | { |
116 | struct device_node *np; |
117 | |
118 | if (soc_dev_attr->machine) |
119 | return; |
120 | |
121 | np = of_find_node_by_path(path: "/" ); |
122 | of_property_read_string(np, propname: "model" , out_string: &soc_dev_attr->machine); |
123 | of_node_put(node: np); |
124 | } |
125 | |
126 | static struct soc_device_attribute *early_soc_dev_attr; |
127 | |
128 | struct soc_device *soc_device_register(struct soc_device_attribute *soc_dev_attr) |
129 | { |
130 | struct soc_device *soc_dev; |
131 | const struct attribute_group **soc_attr_groups; |
132 | int ret; |
133 | |
134 | soc_device_get_machine(soc_dev_attr); |
135 | |
136 | if (!soc_bus_registered) { |
137 | if (early_soc_dev_attr) |
138 | return ERR_PTR(error: -EBUSY); |
139 | early_soc_dev_attr = soc_dev_attr; |
140 | return NULL; |
141 | } |
142 | |
143 | soc_dev = kzalloc(size: sizeof(*soc_dev), GFP_KERNEL); |
144 | if (!soc_dev) { |
145 | ret = -ENOMEM; |
146 | goto out1; |
147 | } |
148 | |
149 | soc_attr_groups = kcalloc(n: 3, size: sizeof(*soc_attr_groups), GFP_KERNEL); |
150 | if (!soc_attr_groups) { |
151 | ret = -ENOMEM; |
152 | goto out2; |
153 | } |
154 | soc_attr_groups[0] = &soc_attr_group; |
155 | soc_attr_groups[1] = soc_dev_attr->custom_attr_group; |
156 | |
157 | /* Fetch a unique (reclaimable) SOC ID. */ |
158 | ret = ida_simple_get(&soc_ida, 0, 0, GFP_KERNEL); |
159 | if (ret < 0) |
160 | goto out3; |
161 | soc_dev->soc_dev_num = ret; |
162 | |
163 | soc_dev->attr = soc_dev_attr; |
164 | soc_dev->dev.bus = &soc_bus_type; |
165 | soc_dev->dev.groups = soc_attr_groups; |
166 | soc_dev->dev.release = soc_release; |
167 | |
168 | dev_set_name(dev: &soc_dev->dev, name: "soc%d" , soc_dev->soc_dev_num); |
169 | |
170 | ret = device_register(dev: &soc_dev->dev); |
171 | if (ret) { |
172 | put_device(dev: &soc_dev->dev); |
173 | return ERR_PTR(error: ret); |
174 | } |
175 | |
176 | return soc_dev; |
177 | |
178 | out3: |
179 | kfree(objp: soc_attr_groups); |
180 | out2: |
181 | kfree(objp: soc_dev); |
182 | out1: |
183 | return ERR_PTR(error: ret); |
184 | } |
185 | EXPORT_SYMBOL_GPL(soc_device_register); |
186 | |
187 | /* Ensure soc_dev->attr is freed after calling soc_device_unregister. */ |
188 | void soc_device_unregister(struct soc_device *soc_dev) |
189 | { |
190 | device_unregister(dev: &soc_dev->dev); |
191 | early_soc_dev_attr = NULL; |
192 | } |
193 | EXPORT_SYMBOL_GPL(soc_device_unregister); |
194 | |
195 | static int __init soc_bus_register(void) |
196 | { |
197 | int ret; |
198 | |
199 | ret = bus_register(bus: &soc_bus_type); |
200 | if (ret) |
201 | return ret; |
202 | soc_bus_registered = true; |
203 | |
204 | if (early_soc_dev_attr) |
205 | return PTR_ERR(ptr: soc_device_register(early_soc_dev_attr)); |
206 | |
207 | return 0; |
208 | } |
209 | core_initcall(soc_bus_register); |
210 | |
211 | static int soc_device_match_attr(const struct soc_device_attribute *attr, |
212 | const struct soc_device_attribute *match) |
213 | { |
214 | if (match->machine && |
215 | (!attr->machine || !glob_match(pat: match->machine, str: attr->machine))) |
216 | return 0; |
217 | |
218 | if (match->family && |
219 | (!attr->family || !glob_match(pat: match->family, str: attr->family))) |
220 | return 0; |
221 | |
222 | if (match->revision && |
223 | (!attr->revision || !glob_match(pat: match->revision, str: attr->revision))) |
224 | return 0; |
225 | |
226 | if (match->soc_id && |
227 | (!attr->soc_id || !glob_match(pat: match->soc_id, str: attr->soc_id))) |
228 | return 0; |
229 | |
230 | return 1; |
231 | } |
232 | |
233 | static int soc_device_match_one(struct device *dev, void *arg) |
234 | { |
235 | struct soc_device *soc_dev = container_of(dev, struct soc_device, dev); |
236 | |
237 | return soc_device_match_attr(attr: soc_dev->attr, match: arg); |
238 | } |
239 | |
240 | /* |
241 | * soc_device_match - identify the SoC in the machine |
242 | * @matches: zero-terminated array of possible matches |
243 | * |
244 | * returns the first matching entry of the argument array, or NULL |
245 | * if none of them match. |
246 | * |
247 | * This function is meant as a helper in place of of_match_node() |
248 | * in cases where either no device tree is available or the information |
249 | * in a device node is insufficient to identify a particular variant |
250 | * by its compatible strings or other properties. For new devices, |
251 | * the DT binding should always provide unique compatible strings |
252 | * that allow the use of of_match_node() instead. |
253 | * |
254 | * The calling function can use the .data entry of the |
255 | * soc_device_attribute to pass a structure or function pointer for |
256 | * each entry. |
257 | */ |
258 | const struct soc_device_attribute *soc_device_match( |
259 | const struct soc_device_attribute *matches) |
260 | { |
261 | int ret; |
262 | |
263 | if (!matches) |
264 | return NULL; |
265 | |
266 | while (matches->machine || matches->family || matches->revision || |
267 | matches->soc_id) { |
268 | ret = bus_for_each_dev(bus: &soc_bus_type, NULL, data: (void *)matches, |
269 | fn: soc_device_match_one); |
270 | if (ret < 0 && early_soc_dev_attr) |
271 | ret = soc_device_match_attr(attr: early_soc_dev_attr, |
272 | match: matches); |
273 | if (ret < 0) |
274 | return NULL; |
275 | if (ret) |
276 | return matches; |
277 | |
278 | matches++; |
279 | } |
280 | return NULL; |
281 | } |
282 | EXPORT_SYMBOL_GPL(soc_device_match); |
283 | |