1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * UCSI ACPI driver
4 *
5 * Copyright (C) 2017, Intel Corporation
6 * Author: Heikki Krogerus <[email protected]>
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
ucsi_acpi_dsm(struct ucsi_acpi * ua,int func)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), &ua->guid, 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
ucsi_acpi_read_version(struct ucsi * ucsi,u16 * version)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
ucsi_acpi_read_cci(struct ucsi * ucsi,u32 * cci)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
ucsi_acpi_poll_cci(struct ucsi * ucsi,u32 * cci)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
ucsi_acpi_read_message_in(struct ucsi * ucsi,void * val,size_t val_len)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
ucsi_acpi_async_control(struct ucsi * ucsi,u64 command)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
ucsi_gram_read_message_in(struct ucsi * ucsi,void * val,size_t val_len)108 static int ucsi_gram_read_message_in(struct ucsi *ucsi, void *val, size_t val_len)
109 {
110 u16 bogus_change = UCSI_CONSTAT_POWER_LEVEL_CHANGE |
111 UCSI_CONSTAT_PDOS_CHANGE;
112 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
113 int ret;
114
115 ret = ucsi_acpi_read_message_in(ucsi, val, val_len);
116 if (ret < 0)
117 return ret;
118
119 if (UCSI_COMMAND(ua->cmd) == UCSI_GET_CONNECTOR_STATUS &&
120 ua->check_bogus_event) {
121 /* Clear the bogus change */
122 if (*(u16 *)val == bogus_change)
123 *(u16 *)val = 0;
124
125 ua->check_bogus_event = false;
126 }
127
128 return ret;
129 }
130
ucsi_gram_sync_control(struct ucsi * ucsi,u64 command)131 static int ucsi_gram_sync_control(struct ucsi *ucsi, u64 command)
132 {
133 struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
134 int ret;
135
136 ret = ucsi_sync_control_common(ucsi, command);
137 if (ret < 0)
138 return ret;
139
140 if (UCSI_COMMAND(ua->cmd) == UCSI_GET_PDOS &&
141 ua->cmd & UCSI_GET_PDOS_PARTNER_PDO(1) &&
142 ua->cmd & UCSI_GET_PDOS_SRC_PDOS)
143 ua->check_bogus_event = true;
144
145 return ret;
146 }
147
148 static const struct ucsi_operations ucsi_gram_ops = {
149 .read_version = ucsi_acpi_read_version,
150 .read_cci = ucsi_acpi_read_cci,
151 .poll_cci = ucsi_acpi_poll_cci,
152 .read_message_in = ucsi_gram_read_message_in,
153 .sync_control = ucsi_gram_sync_control,
154 .async_control = ucsi_acpi_async_control
155 };
156
157 static const struct dmi_system_id ucsi_acpi_quirks[] = {
158 {
159 .matches = {
160 DMI_MATCH(DMI_SYS_VENDOR, "LG Electronics"),
161 DMI_MATCH(DMI_PRODUCT_FAMILY, "LG gram PC"),
162 DMI_MATCH(DMI_PRODUCT_NAME, "90Q"),
163 },
164 .driver_data = (void *)&ucsi_gram_ops,
165 },
166 { }
167 };
168
ucsi_acpi_notify(acpi_handle handle,u32 event,void * data)169 static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data)
170 {
171 struct ucsi_acpi *ua = data;
172 u32 cci;
173 int ret;
174
175 ret = ua->ucsi->ops->read_cci(ua->ucsi, &cci);
176 if (ret)
177 return;
178
179 ucsi_notify_common(ua->ucsi, cci);
180 }
181
ucsi_acpi_probe(struct platform_device * pdev)182 static int ucsi_acpi_probe(struct platform_device *pdev)
183 {
184 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
185 const struct ucsi_operations *ops = &ucsi_acpi_ops;
186 const struct dmi_system_id *id;
187 struct ucsi_acpi *ua;
188 struct resource *res;
189 acpi_status status;
190 int ret;
191
192 if (adev->dep_unmet)
193 return -EPROBE_DEFER;
194
195 ua = devm_kzalloc(&pdev->dev, sizeof(*ua), GFP_KERNEL);
196 if (!ua)
197 return -ENOMEM;
198
199 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
200 if (!res) {
201 dev_err(&pdev->dev, "missing memory resource\n");
202 return -ENODEV;
203 }
204
205 ua->base = devm_memremap(&pdev->dev, res->start, resource_size(res), MEMREMAP_WB);
206 if (IS_ERR(ua->base))
207 return PTR_ERR(ua->base);
208
209 ret = guid_parse(UCSI_DSM_UUID, &ua->guid);
210 if (ret)
211 return ret;
212
213 ua->dev = &pdev->dev;
214
215 id = dmi_first_match(ucsi_acpi_quirks);
216 if (id)
217 ops = id->driver_data;
218
219 ua->ucsi = ucsi_create(&pdev->dev, ops);
220 if (IS_ERR(ua->ucsi))
221 return PTR_ERR(ua->ucsi);
222
223 ucsi_set_drvdata(ua->ucsi, ua);
224
225 status = acpi_install_notify_handler(ACPI_HANDLE(&pdev->dev),
226 ACPI_DEVICE_NOTIFY,
227 ucsi_acpi_notify, ua);
228 if (ACPI_FAILURE(status)) {
229 dev_err(&pdev->dev, "failed to install notify handler\n");
230 ucsi_destroy(ua->ucsi);
231 return -ENODEV;
232 }
233
234 ret = ucsi_register(ua->ucsi);
235 if (ret) {
236 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev),
237 ACPI_DEVICE_NOTIFY,
238 ucsi_acpi_notify);
239 ucsi_destroy(ua->ucsi);
240 return ret;
241 }
242
243 platform_set_drvdata(pdev, ua);
244
245 return 0;
246 }
247
ucsi_acpi_remove(struct platform_device * pdev)248 static void ucsi_acpi_remove(struct platform_device *pdev)
249 {
250 struct ucsi_acpi *ua = platform_get_drvdata(pdev);
251
252 ucsi_unregister(ua->ucsi);
253 ucsi_destroy(ua->ucsi);
254
255 acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY,
256 ucsi_acpi_notify);
257 }
258
ucsi_acpi_resume(struct device * dev)259 static int ucsi_acpi_resume(struct device *dev)
260 {
261 struct ucsi_acpi *ua = dev_get_drvdata(dev);
262
263 return ucsi_resume(ua->ucsi);
264 }
265
266 static DEFINE_SIMPLE_DEV_PM_OPS(ucsi_acpi_pm_ops, NULL, ucsi_acpi_resume);
267
268 static const struct acpi_device_id ucsi_acpi_match[] = {
269 { "PNP0CA0", 0 },
270 { },
271 };
272 MODULE_DEVICE_TABLE(acpi, ucsi_acpi_match);
273
274 static struct platform_driver ucsi_acpi_platform_driver = {
275 .driver = {
276 .name = "ucsi_acpi",
277 .pm = pm_ptr(&ucsi_acpi_pm_ops),
278 .acpi_match_table = ACPI_PTR(ucsi_acpi_match),
279 },
280 .probe = ucsi_acpi_probe,
281 .remove = ucsi_acpi_remove,
282 };
283
284 module_platform_driver(ucsi_acpi_platform_driver);
285
286 MODULE_AUTHOR("Heikki Krogerus <[email protected]>");
287 MODULE_LICENSE("GPL v2");
288 MODULE_DESCRIPTION("UCSI ACPI driver");
289