1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Intel PCH pinctrl/GPIO driver |
4 | * |
5 | * Copyright (C) 2021-2023, Intel Corporation |
6 | * Author: Andy Shevchenko <andriy.shevchenko@linux.intel.com> |
7 | */ |
8 | |
9 | #include <linux/mod_devicetable.h> |
10 | #include <linux/module.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/pm.h> |
13 | #include <linux/property.h> |
14 | #include <linux/string_helpers.h> |
15 | |
16 | #include <linux/pinctrl/pinctrl.h> |
17 | |
18 | #include "pinctrl-intel.h" |
19 | |
20 | struct intel_platform_pins { |
21 | struct pinctrl_pin_desc *pins; |
22 | size_t npins; |
23 | }; |
24 | |
25 | static int intel_platform_pinctrl_prepare_pins(struct device *dev, size_t base, |
26 | const char *name, u32 size, |
27 | struct intel_platform_pins *pins) |
28 | { |
29 | struct pinctrl_pin_desc *descs; |
30 | char **pin_names; |
31 | unsigned int i; |
32 | |
33 | pin_names = devm_kasprintf_strarray(dev, prefix: name, n: size); |
34 | if (IS_ERR(ptr: pin_names)) |
35 | return PTR_ERR(ptr: pin_names); |
36 | |
37 | descs = devm_krealloc_array(dev, p: pins->pins, new_n: base + size, new_size: sizeof(*descs), GFP_KERNEL); |
38 | if (!descs) |
39 | return -ENOMEM; |
40 | |
41 | for (i = 0; i < size; i++) { |
42 | unsigned int pin_number = base + i; |
43 | char *pin_name = pin_names[i]; |
44 | struct pinctrl_pin_desc *desc; |
45 | |
46 | /* Unify delimiter for pin name */ |
47 | strreplace(str: pin_name, old: '-', new: '_'); |
48 | |
49 | desc = &descs[pin_number]; |
50 | desc->number = pin_number; |
51 | desc->name = pin_name; |
52 | } |
53 | |
54 | pins->pins = descs; |
55 | pins->npins = base + size; |
56 | |
57 | return 0; |
58 | } |
59 | |
60 | static int intel_platform_pinctrl_prepare_group(struct device *dev, |
61 | struct fwnode_handle *child, |
62 | struct intel_padgroup *gpp, |
63 | struct intel_platform_pins *pins) |
64 | { |
65 | size_t base = pins->npins; |
66 | const char *name; |
67 | u32 size; |
68 | int ret; |
69 | |
70 | ret = fwnode_property_read_string(fwnode: child, propname: "intc-gpio-group-name" , val: &name); |
71 | if (ret) |
72 | return ret; |
73 | |
74 | ret = fwnode_property_read_u32(fwnode: child, propname: "intc-gpio-pad-count" , val: &size); |
75 | if (ret) |
76 | return ret; |
77 | |
78 | ret = intel_platform_pinctrl_prepare_pins(dev, base, name, size, pins); |
79 | if (ret) |
80 | return ret; |
81 | |
82 | gpp->base = base; |
83 | gpp->size = size; |
84 | gpp->gpio_base = INTEL_GPIO_BASE_MATCH; |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int (struct device *dev, |
90 | struct intel_community *, |
91 | struct intel_platform_pins *pins) |
92 | { |
93 | struct fwnode_handle *child; |
94 | struct intel_padgroup *gpps; |
95 | unsigned int group; |
96 | size_t ngpps; |
97 | u32 offset; |
98 | int ret; |
99 | |
100 | ret = device_property_read_u32(dev, propname: "intc-gpio-pad-ownership-offset" , val: &offset); |
101 | if (ret) |
102 | return ret; |
103 | community->padown_offset = offset; |
104 | |
105 | ret = device_property_read_u32(dev, propname: "intc-gpio-pad-configuration-lock-offset" , val: &offset); |
106 | if (ret) |
107 | return ret; |
108 | community->padcfglock_offset = offset; |
109 | |
110 | ret = device_property_read_u32(dev, propname: "intc-gpio-host-software-pad-ownership-offset" , val: &offset); |
111 | if (ret) |
112 | return ret; |
113 | community->hostown_offset = offset; |
114 | |
115 | ret = device_property_read_u32(dev, propname: "intc-gpio-gpi-interrupt-status-offset" , val: &offset); |
116 | if (ret) |
117 | return ret; |
118 | community->is_offset = offset; |
119 | |
120 | ret = device_property_read_u32(dev, propname: "intc-gpio-gpi-interrupt-enable-offset" , val: &offset); |
121 | if (ret) |
122 | return ret; |
123 | community->ie_offset = offset; |
124 | |
125 | ngpps = device_get_child_node_count(dev); |
126 | if (!ngpps) |
127 | return -ENODEV; |
128 | |
129 | gpps = devm_kcalloc(dev, n: ngpps, size: sizeof(*gpps), GFP_KERNEL); |
130 | if (!gpps) |
131 | return -ENOMEM; |
132 | |
133 | group = 0; |
134 | device_for_each_child_node(dev, child) { |
135 | struct intel_padgroup *gpp = &gpps[group]; |
136 | |
137 | gpp->reg_num = group; |
138 | |
139 | ret = intel_platform_pinctrl_prepare_group(dev, child, gpp, pins); |
140 | if (ret) |
141 | return ret; |
142 | |
143 | group++; |
144 | } |
145 | |
146 | community->ngpps = ngpps; |
147 | community->gpps = gpps; |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static int intel_platform_pinctrl_prepare_soc_data(struct device *dev, |
153 | struct intel_pinctrl_soc_data *data) |
154 | { |
155 | struct intel_platform_pins pins = {}; |
156 | struct intel_community *communities; |
157 | size_t ncommunities; |
158 | unsigned int i; |
159 | int ret; |
160 | |
161 | /* Version 1.0 of the specification assumes only a single community per device node */ |
162 | ncommunities = 1, |
163 | communities = devm_kcalloc(dev, n: ncommunities, size: sizeof(*communities), GFP_KERNEL); |
164 | if (!communities) |
165 | return -ENOMEM; |
166 | |
167 | for (i = 0; i < ncommunities; i++) { |
168 | struct intel_community * = &communities[i]; |
169 | |
170 | community->barno = i; |
171 | community->pin_base = pins.npins; |
172 | |
173 | ret = intel_platform_pinctrl_prepare_community(dev, community, pins: &pins); |
174 | if (ret) |
175 | return ret; |
176 | |
177 | community->npins = pins.npins - community->pin_base; |
178 | } |
179 | |
180 | data->ncommunities = ncommunities; |
181 | data->communities = communities; |
182 | |
183 | data->npins = pins.npins; |
184 | data->pins = pins.pins; |
185 | |
186 | return 0; |
187 | } |
188 | |
189 | static int intel_platform_pinctrl_probe(struct platform_device *pdev) |
190 | { |
191 | struct intel_pinctrl_soc_data *data; |
192 | struct device *dev = &pdev->dev; |
193 | int ret; |
194 | |
195 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
196 | if (!data) |
197 | return -ENOMEM; |
198 | |
199 | ret = intel_platform_pinctrl_prepare_soc_data(dev, data); |
200 | if (ret) |
201 | return ret; |
202 | |
203 | return intel_pinctrl_probe(pdev, soc_data: data); |
204 | } |
205 | |
206 | static const struct acpi_device_id intel_platform_pinctrl_acpi_match[] = { |
207 | { "INTC105F" }, |
208 | { } |
209 | }; |
210 | MODULE_DEVICE_TABLE(acpi, intel_platform_pinctrl_acpi_match); |
211 | |
212 | static struct platform_driver intel_platform_pinctrl_driver = { |
213 | .probe = intel_platform_pinctrl_probe, |
214 | .driver = { |
215 | .name = "intel-pinctrl" , |
216 | .acpi_match_table = intel_platform_pinctrl_acpi_match, |
217 | .pm = pm_sleep_ptr(&intel_pinctrl_pm_ops), |
218 | }, |
219 | }; |
220 | module_platform_driver(intel_platform_pinctrl_driver); |
221 | |
222 | MODULE_AUTHOR("Andy Shevchenko <andriy.shevchenko@linux.intel.com>" ); |
223 | MODULE_DESCRIPTION("Intel PCH pinctrl/GPIO driver" ); |
224 | MODULE_LICENSE("GPL v2" ); |
225 | MODULE_IMPORT_NS(PINCTRL_INTEL); |
226 | |