Commit | Line | Data |
---|---|---|
a10e763b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
91087dfa AS |
2 | /* |
3 | * SMBus driver for ACPI Embedded Controller (v0.1) | |
4 | * | |
5 | * Copyright (c) 2007 Alexey Starikovskiy | |
91087dfa AS |
6 | */ |
7 | ||
bd10c13b HG |
8 | #define pr_fmt(fmt) "ACPI: " fmt |
9 | ||
8b48463f | 10 | #include <linux/acpi.h> |
91087dfa | 11 | #include <linux/wait.h> |
5a0e3ad6 | 12 | #include <linux/slab.h> |
91087dfa | 13 | #include <linux/delay.h> |
cc4b859c | 14 | #include <linux/module.h> |
91087dfa AS |
15 | #include <linux/interrupt.h> |
16 | #include "sbshc.h" | |
17 | ||
97c227cb | 18 | #define ACPI_SMB_HC_CLASS "smbus_host_ctl" |
91087dfa AS |
19 | #define ACPI_SMB_HC_DEVICE_NAME "ACPI SMBus HC" |
20 | ||
21 | struct acpi_smb_hc { | |
22 | struct acpi_ec *ec; | |
23 | struct mutex lock; | |
24 | wait_queue_head_t wait; | |
25 | u8 offset; | |
26 | u8 query_bit; | |
27 | smbus_alarm_callback callback; | |
28 | void *context; | |
add68d6a | 29 | bool done; |
91087dfa AS |
30 | }; |
31 | ||
32 | static int acpi_smbus_hc_add(struct acpi_device *device); | |
51fac838 | 33 | static int acpi_smbus_hc_remove(struct acpi_device *device); |
91087dfa AS |
34 | |
35 | static const struct acpi_device_id sbs_device_ids[] = { | |
36 | {"ACPI0001", 0}, | |
37 | {"ACPI0005", 0}, | |
38 | {"", 0}, | |
39 | }; | |
40 | ||
41 | MODULE_DEVICE_TABLE(acpi, sbs_device_ids); | |
42 | ||
43 | static struct acpi_driver acpi_smb_hc_driver = { | |
44 | .name = "smbus_hc", | |
45 | .class = ACPI_SMB_HC_CLASS, | |
46 | .ids = sbs_device_ids, | |
47 | .ops = { | |
48 | .add = acpi_smbus_hc_add, | |
49 | .remove = acpi_smbus_hc_remove, | |
50 | }, | |
51 | }; | |
52 | ||
53 | union acpi_smb_status { | |
54 | u8 raw; | |
55 | struct { | |
56 | u8 status:5; | |
57 | u8 reserved:1; | |
58 | u8 alarm:1; | |
59 | u8 done:1; | |
60 | } fields; | |
61 | }; | |
62 | ||
63 | enum acpi_smb_status_codes { | |
64 | SMBUS_OK = 0, | |
65 | SMBUS_UNKNOWN_FAILURE = 0x07, | |
66 | SMBUS_DEVICE_ADDRESS_NACK = 0x10, | |
67 | SMBUS_DEVICE_ERROR = 0x11, | |
68 | SMBUS_DEVICE_COMMAND_ACCESS_DENIED = 0x12, | |
69 | SMBUS_UNKNOWN_ERROR = 0x13, | |
70 | SMBUS_DEVICE_ACCESS_DENIED = 0x17, | |
71 | SMBUS_TIMEOUT = 0x18, | |
72 | SMBUS_HOST_UNSUPPORTED_PROTOCOL = 0x19, | |
73 | SMBUS_BUSY = 0x1a, | |
74 | SMBUS_PEC_ERROR = 0x1f, | |
75 | }; | |
76 | ||
77 | enum acpi_smb_offset { | |
78 | ACPI_SMB_PROTOCOL = 0, /* protocol, PEC */ | |
79 | ACPI_SMB_STATUS = 1, /* status */ | |
80 | ACPI_SMB_ADDRESS = 2, /* address */ | |
81 | ACPI_SMB_COMMAND = 3, /* command */ | |
82 | ACPI_SMB_DATA = 4, /* 32 data registers */ | |
83 | ACPI_SMB_BLOCK_COUNT = 0x24, /* number of data bytes */ | |
84 | ACPI_SMB_ALARM_ADDRESS = 0x25, /* alarm address */ | |
85 | ACPI_SMB_ALARM_DATA = 0x26, /* 2 bytes alarm data */ | |
86 | }; | |
87 | ||
88 | static inline int smb_hc_read(struct acpi_smb_hc *hc, u8 address, u8 *data) | |
89 | { | |
90 | return ec_read(hc->offset + address, data); | |
91 | } | |
92 | ||
93 | static inline int smb_hc_write(struct acpi_smb_hc *hc, u8 address, u8 data) | |
94 | { | |
95 | return ec_write(hc->offset + address, data); | |
96 | } | |
97 | ||
91087dfa AS |
98 | static int wait_transaction_complete(struct acpi_smb_hc *hc, int timeout) |
99 | { | |
add68d6a | 100 | if (wait_event_timeout(hc->wait, hc->done, msecs_to_jiffies(timeout))) |
91087dfa | 101 | return 0; |
add68d6a | 102 | return -ETIME; |
91087dfa AS |
103 | } |
104 | ||
e5685b9d AB |
105 | static int acpi_smbus_transaction(struct acpi_smb_hc *hc, u8 protocol, |
106 | u8 address, u8 command, u8 *data, u8 length) | |
91087dfa AS |
107 | { |
108 | int ret = -EFAULT, i; | |
109 | u8 temp, sz = 0; | |
110 | ||
bbafbecb | 111 | if (!hc) { |
bd10c13b | 112 | pr_err("host controller is not configured\n"); |
bbafbecb AS |
113 | return ret; |
114 | } | |
115 | ||
91087dfa | 116 | mutex_lock(&hc->lock); |
add68d6a | 117 | hc->done = false; |
91087dfa AS |
118 | if (smb_hc_read(hc, ACPI_SMB_PROTOCOL, &temp)) |
119 | goto end; | |
120 | if (temp) { | |
121 | ret = -EBUSY; | |
122 | goto end; | |
123 | } | |
124 | smb_hc_write(hc, ACPI_SMB_COMMAND, command); | |
91087dfa AS |
125 | if (!(protocol & 0x01)) { |
126 | smb_hc_write(hc, ACPI_SMB_BLOCK_COUNT, length); | |
127 | for (i = 0; i < length; ++i) | |
128 | smb_hc_write(hc, ACPI_SMB_DATA + i, data[i]); | |
129 | } | |
130 | smb_hc_write(hc, ACPI_SMB_ADDRESS, address << 1); | |
131 | smb_hc_write(hc, ACPI_SMB_PROTOCOL, protocol); | |
132 | /* | |
133 | * Wait for completion. Save the status code, data size, | |
134 | * and data into the return package (if required by the protocol). | |
135 | */ | |
136 | ret = wait_transaction_complete(hc, 1000); | |
137 | if (ret || !(protocol & 0x01)) | |
138 | goto end; | |
139 | switch (protocol) { | |
140 | case SMBUS_RECEIVE_BYTE: | |
141 | case SMBUS_READ_BYTE: | |
142 | sz = 1; | |
143 | break; | |
144 | case SMBUS_READ_WORD: | |
145 | sz = 2; | |
146 | break; | |
147 | case SMBUS_READ_BLOCK: | |
148 | if (smb_hc_read(hc, ACPI_SMB_BLOCK_COUNT, &sz)) { | |
149 | ret = -EFAULT; | |
150 | goto end; | |
151 | } | |
152 | sz &= 0x1f; | |
153 | break; | |
154 | } | |
155 | for (i = 0; i < sz; ++i) | |
156 | smb_hc_read(hc, ACPI_SMB_DATA + i, &data[i]); | |
157 | end: | |
158 | mutex_unlock(&hc->lock); | |
159 | return ret; | |
160 | } | |
161 | ||
162 | int acpi_smbus_read(struct acpi_smb_hc *hc, u8 protocol, u8 address, | |
163 | u8 command, u8 *data) | |
164 | { | |
165 | return acpi_smbus_transaction(hc, protocol, address, command, data, 0); | |
166 | } | |
167 | ||
168 | EXPORT_SYMBOL_GPL(acpi_smbus_read); | |
169 | ||
170 | int acpi_smbus_write(struct acpi_smb_hc *hc, u8 protocol, u8 address, | |
171 | u8 command, u8 *data, u8 length) | |
172 | { | |
173 | return acpi_smbus_transaction(hc, protocol, address, command, data, length); | |
174 | } | |
175 | ||
176 | EXPORT_SYMBOL_GPL(acpi_smbus_write); | |
177 | ||
178 | int acpi_smbus_register_callback(struct acpi_smb_hc *hc, | |
c6237b21 | 179 | smbus_alarm_callback callback, void *context) |
91087dfa AS |
180 | { |
181 | mutex_lock(&hc->lock); | |
182 | hc->callback = callback; | |
183 | hc->context = context; | |
184 | mutex_unlock(&hc->lock); | |
185 | return 0; | |
186 | } | |
187 | ||
188 | EXPORT_SYMBOL_GPL(acpi_smbus_register_callback); | |
189 | ||
190 | int acpi_smbus_unregister_callback(struct acpi_smb_hc *hc) | |
191 | { | |
192 | mutex_lock(&hc->lock); | |
193 | hc->callback = NULL; | |
194 | hc->context = NULL; | |
195 | mutex_unlock(&hc->lock); | |
757c968c | 196 | acpi_os_wait_events_complete(); |
91087dfa AS |
197 | return 0; |
198 | } | |
199 | ||
200 | EXPORT_SYMBOL_GPL(acpi_smbus_unregister_callback); | |
201 | ||
c2d00f2d | 202 | static inline void acpi_smbus_callback(void *context) |
91087dfa AS |
203 | { |
204 | struct acpi_smb_hc *hc = context; | |
91087dfa AS |
205 | if (hc->callback) |
206 | hc->callback(hc->context); | |
207 | } | |
208 | ||
209 | static int smbus_alarm(void *context) | |
210 | { | |
211 | struct acpi_smb_hc *hc = context; | |
212 | union acpi_smb_status status; | |
c2d00f2d | 213 | u8 address; |
91087dfa AS |
214 | if (smb_hc_read(hc, ACPI_SMB_STATUS, &status.raw)) |
215 | return 0; | |
216 | /* Check if it is only a completion notify */ | |
add68d6a CB |
217 | if (status.fields.done && status.fields.status == SMBUS_OK) { |
218 | hc->done = true; | |
91087dfa | 219 | wake_up(&hc->wait); |
add68d6a | 220 | } |
91087dfa AS |
221 | if (!status.fields.alarm) |
222 | return 0; | |
223 | mutex_lock(&hc->lock); | |
c2d00f2d | 224 | smb_hc_read(hc, ACPI_SMB_ALARM_ADDRESS, &address); |
09f1fb41 | 225 | status.fields.alarm = 0; |
91087dfa | 226 | smb_hc_write(hc, ACPI_SMB_STATUS, status.raw); |
c2d00f2d AS |
227 | /* We are only interested in events coming from known devices */ |
228 | switch (address >> 1) { | |
229 | case ACPI_SBS_CHARGER: | |
230 | case ACPI_SBS_MANAGER: | |
231 | case ACPI_SBS_BATTERY: | |
f5347867 | 232 | acpi_os_execute(OSL_NOTIFY_HANDLER, |
c2d00f2d | 233 | acpi_smbus_callback, hc); |
c2d00f2d | 234 | } |
91087dfa AS |
235 | mutex_unlock(&hc->lock); |
236 | return 0; | |
237 | } | |
238 | ||
239 | typedef int (*acpi_ec_query_func) (void *data); | |
240 | ||
241 | extern int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit, | |
242 | acpi_handle handle, acpi_ec_query_func func, | |
243 | void *data); | |
244 | ||
245 | static int acpi_smbus_hc_add(struct acpi_device *device) | |
246 | { | |
247 | int status; | |
27663c58 | 248 | unsigned long long val; |
91087dfa AS |
249 | struct acpi_smb_hc *hc; |
250 | ||
251 | if (!device) | |
252 | return -EINVAL; | |
253 | ||
254 | status = acpi_evaluate_integer(device->handle, "_EC", NULL, &val); | |
255 | if (ACPI_FAILURE(status)) { | |
bd10c13b | 256 | pr_err("error obtaining _EC.\n"); |
91087dfa AS |
257 | return -EIO; |
258 | } | |
259 | ||
260 | strcpy(acpi_device_name(device), ACPI_SMB_HC_DEVICE_NAME); | |
261 | strcpy(acpi_device_class(device), ACPI_SMB_HC_CLASS); | |
262 | ||
263 | hc = kzalloc(sizeof(struct acpi_smb_hc), GFP_KERNEL); | |
264 | if (!hc) | |
265 | return -ENOMEM; | |
266 | mutex_init(&hc->lock); | |
267 | init_waitqueue_head(&hc->wait); | |
268 | ||
269 | hc->ec = acpi_driver_data(device->parent); | |
270 | hc->offset = (val >> 8) & 0xff; | |
271 | hc->query_bit = val & 0xff; | |
db89b4f0 | 272 | device->driver_data = hc; |
91087dfa AS |
273 | |
274 | acpi_ec_add_query_handler(hc->ec, hc->query_bit, NULL, smbus_alarm, hc); | |
43cdd1b7 GKH |
275 | dev_info(&device->dev, "SBS HC: offset = 0x%0x, query_bit = 0x%0x\n", |
276 | hc->offset, hc->query_bit); | |
91087dfa AS |
277 | |
278 | return 0; | |
279 | } | |
280 | ||
281 | extern void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit); | |
282 | ||
51fac838 | 283 | static int acpi_smbus_hc_remove(struct acpi_device *device) |
91087dfa AS |
284 | { |
285 | struct acpi_smb_hc *hc; | |
286 | ||
287 | if (!device) | |
288 | return -EINVAL; | |
289 | ||
290 | hc = acpi_driver_data(device); | |
291 | acpi_ec_remove_query_handler(hc->ec, hc->query_bit); | |
757c968c | 292 | acpi_os_wait_events_complete(); |
91087dfa | 293 | kfree(hc); |
db89b4f0 | 294 | device->driver_data = NULL; |
91087dfa AS |
295 | return 0; |
296 | } | |
297 | ||
e84a239e | 298 | module_acpi_driver(acpi_smb_hc_driver); |
91087dfa AS |
299 | |
300 | MODULE_LICENSE("GPL"); | |
301 | MODULE_AUTHOR("Alexey Starikovskiy"); | |
302 | MODULE_DESCRIPTION("ACPI SMBus HC driver"); |