1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * UCSI ACPI driver |
4 | * |
5 | * Copyright (C) 2017, Intel Corporation |
6 | * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> |
7 | */ |
8 | |
9 | #include <linux/platform_device.h> |
10 | #include <linux/module.h> |
11 | #include <linux/acpi.h> |
12 | #include <linux/dmi.h> |
13 | |
14 | #include "ucsi.h" |
15 | |
16 | #define UCSI_DSM_UUID "6f8398c2-7ca4-11e4-ad36-631042b5008f" |
17 | #define UCSI_DSM_FUNC_WRITE 1 |
18 | #define UCSI_DSM_FUNC_READ 2 |
19 | |
20 | struct ucsi_acpi { |
21 | struct device *dev; |
22 | struct ucsi *ucsi; |
23 | void *base; |
24 | bool check_bogus_event; |
25 | guid_t guid; |
26 | u64 cmd; |
27 | }; |
28 | |
29 | static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func) |
30 | { |
31 | union acpi_object *obj; |
32 | |
33 | obj = acpi_evaluate_dsm(ACPI_HANDLE(ua->dev), guid: &ua->guid, rev: 1, func, |
34 | NULL); |
35 | if (!obj) { |
36 | dev_err(ua->dev, "%s: failed to evaluate _DSM %d\n" , |
37 | __func__, func); |
38 | return -EIO; |
39 | } |
40 | |
41 | ACPI_FREE(obj); |
42 | return 0; |
43 | } |
44 | |
45 | static int ucsi_acpi_read_version(struct ucsi *ucsi, u16 *version) |
46 | { |
47 | struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); |
48 | int ret; |
49 | |
50 | ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); |
51 | if (ret) |
52 | return ret; |
53 | |
54 | memcpy(version, ua->base + UCSI_VERSION, sizeof(*version)); |
55 | |
56 | return 0; |
57 | } |
58 | |
59 | static int ucsi_acpi_read_cci(struct ucsi *ucsi, u32 *cci) |
60 | { |
61 | struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); |
62 | |
63 | memcpy(cci, ua->base + UCSI_CCI, sizeof(*cci)); |
64 | |
65 | return 0; |
66 | } |
67 | |
68 | static int ucsi_acpi_poll_cci(struct ucsi *ucsi, u32 *cci) |
69 | { |
70 | struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); |
71 | int ret; |
72 | |
73 | ret = ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_READ); |
74 | if (ret) |
75 | return ret; |
76 | |
77 | return ucsi_acpi_read_cci(ucsi, cci); |
78 | } |
79 | |
80 | static int ucsi_acpi_read_message_in(struct ucsi *ucsi, void *val, size_t val_len) |
81 | { |
82 | struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); |
83 | |
84 | memcpy(val, ua->base + UCSI_MESSAGE_IN, val_len); |
85 | |
86 | return 0; |
87 | } |
88 | |
89 | static int ucsi_acpi_async_control(struct ucsi *ucsi, u64 command) |
90 | { |
91 | struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); |
92 | |
93 | memcpy(ua->base + UCSI_CONTROL, &command, sizeof(command)); |
94 | ua->cmd = command; |
95 | |
96 | return ucsi_acpi_dsm(ua, UCSI_DSM_FUNC_WRITE); |
97 | } |
98 | |
99 | static const struct ucsi_operations ucsi_acpi_ops = { |
100 | .read_version = ucsi_acpi_read_version, |
101 | .read_cci = ucsi_acpi_read_cci, |
102 | .poll_cci = ucsi_acpi_poll_cci, |
103 | .read_message_in = ucsi_acpi_read_message_in, |
104 | .sync_control = ucsi_sync_control_common, |
105 | .async_control = ucsi_acpi_async_control |
106 | }; |
107 | |
108 | static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command, u32 *cci, |
109 | void *val, size_t len) |
110 | { |
111 | u16 bogus_change = UCSI_CONSTAT_POWER_LEVEL_CHANGE | |
112 | UCSI_CONSTAT_PDOS_CHANGE; |
113 | struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi); |
114 | int ret; |
115 | |
116 | ret = ucsi_sync_control_common(ucsi, command, cci, data: val, size: len); |
117 | if (ret < 0) |
118 | return ret; |
119 | |
120 | if (UCSI_COMMAND(ua->cmd) == UCSI_GET_PDOS && |
121 | ua->cmd & UCSI_GET_PDOS_PARTNER_PDO(1) && |
122 | ua->cmd & UCSI_GET_PDOS_SRC_PDOS) |
123 | ua->check_bogus_event = true; |
124 | |
125 | if (UCSI_COMMAND(ua->cmd) == UCSI_GET_CONNECTOR_STATUS && |
126 | ua->check_bogus_event) { |
127 | /* Clear the bogus change */ |
128 | if (*(u16 *)val == bogus_change) |
129 | *(u16 *)val = 0; |
130 | |
131 | ua->check_bogus_event = false; |
132 | } |
133 | |
134 | return ret; |
135 | } |
136 | |
137 | static const struct ucsi_operations ucsi_gram_ops = { |
138 | .read_version = ucsi_acpi_read_version, |
139 | .read_cci = ucsi_acpi_read_cci, |
140 | .poll_cci = ucsi_acpi_poll_cci, |
141 | .read_message_in = ucsi_acpi_read_message_in, |
142 | .sync_control = ucsi_gram_sync_control, |
143 | .async_control = ucsi_acpi_async_control |
144 | }; |
145 | |
146 | static const struct dmi_system_id ucsi_acpi_quirks[] = { |
147 | { |
148 | .matches = { |
149 | DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics" ), |
150 | DMI_MATCH(DMI_PRODUCT_FAMILY, "LG gram PC" ), |
151 | DMI_MATCH(DMI_PRODUCT_NAME, "90Q" ), |
152 | }, |
153 | .driver_data = (void *)&ucsi_gram_ops, |
154 | }, |
155 | { } |
156 | }; |
157 | |
158 | static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data) |
159 | { |
160 | struct ucsi_acpi *ua = data; |
161 | u32 cci; |
162 | int ret; |
163 | |
164 | ret = ua->ucsi->ops->read_cci(ua->ucsi, &cci); |
165 | if (ret) |
166 | return; |
167 | |
168 | ucsi_notify_common(ucsi: ua->ucsi, cci); |
169 | } |
170 | |
171 | static int ucsi_acpi_probe(struct platform_device *pdev) |
172 | { |
173 | struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); |
174 | const struct ucsi_operations *ops = &ucsi_acpi_ops; |
175 | const struct dmi_system_id *id; |
176 | struct ucsi_acpi *ua; |
177 | struct resource *res; |
178 | acpi_status status; |
179 | int ret; |
180 | |
181 | if (adev->dep_unmet) |
182 | return -EPROBE_DEFER; |
183 | |
184 | ua = devm_kzalloc(dev: &pdev->dev, size: sizeof(*ua), GFP_KERNEL); |
185 | if (!ua) |
186 | return -ENOMEM; |
187 | |
188 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
189 | if (!res) { |
190 | dev_err(&pdev->dev, "missing memory resource\n" ); |
191 | return -ENODEV; |
192 | } |
193 | |
194 | ua->base = devm_memremap(dev: &pdev->dev, offset: res->start, size: resource_size(res), flags: MEMREMAP_WB); |
195 | if (IS_ERR(ptr: ua->base)) |
196 | return PTR_ERR(ptr: ua->base); |
197 | |
198 | ret = guid_parse(UCSI_DSM_UUID, u: &ua->guid); |
199 | if (ret) |
200 | return ret; |
201 | |
202 | ua->dev = &pdev->dev; |
203 | |
204 | id = dmi_first_match(list: ucsi_acpi_quirks); |
205 | if (id) |
206 | ops = id->driver_data; |
207 | |
208 | ua->ucsi = ucsi_create(dev: &pdev->dev, ops); |
209 | if (IS_ERR(ptr: ua->ucsi)) |
210 | return PTR_ERR(ptr: ua->ucsi); |
211 | |
212 | ucsi_set_drvdata(ucsi: ua->ucsi, data: ua); |
213 | |
214 | status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev), |
215 | ACPI_DEVICE_NOTIFY, |
216 | handler: ucsi_acpi_notify, context: ua); |
217 | if (ACPI_FAILURE(status)) { |
218 | dev_err(&pdev->dev, "failed to install notify handler\n" ); |
219 | ucsi_destroy(ucsi: ua->ucsi); |
220 | return -ENODEV; |
221 | } |
222 | |
223 | ret = ucsi_register(ucsi: ua->ucsi); |
224 | if (ret) { |
225 | acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), |
226 | ACPI_DEVICE_NOTIFY, |
227 | handler: ucsi_acpi_notify); |
228 | ucsi_destroy(ucsi: ua->ucsi); |
229 | return ret; |
230 | } |
231 | |
232 | platform_set_drvdata(pdev, data: ua); |
233 | |
234 | return 0; |
235 | } |
236 | |
237 | static void ucsi_acpi_remove(struct platform_device *pdev) |
238 | { |
239 | struct ucsi_acpi *ua = platform_get_drvdata(pdev); |
240 | |
241 | ucsi_unregister(ucsi: ua->ucsi); |
242 | ucsi_destroy(ucsi: ua->ucsi); |
243 | |
244 | acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, |
245 | handler: ucsi_acpi_notify); |
246 | } |
247 | |
248 | static int ucsi_acpi_resume(struct device *dev) |
249 | { |
250 | struct ucsi_acpi *ua = dev_get_drvdata(dev); |
251 | |
252 | return ucsi_resume(ucsi: ua->ucsi); |
253 | } |
254 | |
255 | static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_acpi_pm_ops, NULL, ucsi_acpi_resume); |
256 | |
257 | static const struct acpi_device_id ucsi_acpi_match[] = { |
258 | { "PNP0CA0" , 0 }, |
259 | { }, |
260 | }; |
261 | MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match); |
262 | |
263 | static struct platform_driver ucsi_acpi_platform_driver = { |
264 | .driver = { |
265 | .name = "ucsi_acpi" , |
266 | .pm = pm_ptr(&ucsi_acpi_pm_ops), |
267 | .acpi_match_table = ACPI_PTR(ucsi_acpi_match), |
268 | }, |
269 | .probe = ucsi_acpi_probe, |
270 | .remove = ucsi_acpi_remove, |
271 | }; |
272 | |
273 | module_platform_driver(ucsi_acpi_platform_driver); |
274 | |
275 | MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>" ); |
276 | MODULE_LICENSE("GPL v2" ); |
277 | MODULE_DESCRIPTION("UCSI ACPI driver" ); |
278 | |