Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/drivers/acpi/sbshc.c
15111 views
1
/*
2
* SMBus driver for ACPI Embedded Controller (v0.1)
3
*
4
* Copyright (c) 2007 Alexey Starikovskiy
5
*
6
* This program is free software; you can redistribute it and/or modify
7
* it under the terms of the GNU General Public License as published by
8
* the Free Software Foundation version 2.
9
*/
10
11
#include <acpi/acpi_bus.h>
12
#include <acpi/acpi_drivers.h>
13
#include <linux/wait.h>
14
#include <linux/slab.h>
15
#include <linux/delay.h>
16
#include <linux/interrupt.h>
17
#include "sbshc.h"
18
19
#define PREFIX "ACPI: "
20
21
#define ACPI_SMB_HC_CLASS "smbus_host_ctl"
22
#define ACPI_SMB_HC_DEVICE_NAME "ACPI SMBus HC"
23
24
struct acpi_smb_hc {
25
struct acpi_ec *ec;
26
struct mutex lock;
27
wait_queue_head_t wait;
28
u8 offset;
29
u8 query_bit;
30
smbus_alarm_callback callback;
31
void *context;
32
};
33
34
static int acpi_smbus_hc_add(struct acpi_device *device);
35
static int acpi_smbus_hc_remove(struct acpi_device *device, int type);
36
37
static const struct acpi_device_id sbs_device_ids[] = {
38
{"ACPI0001", 0},
39
{"ACPI0005", 0},
40
{"", 0},
41
};
42
43
MODULE_DEVICE_TABLE(acpi, sbs_device_ids);
44
45
static struct acpi_driver acpi_smb_hc_driver = {
46
.name = "smbus_hc",
47
.class = ACPI_SMB_HC_CLASS,
48
.ids = sbs_device_ids,
49
.ops = {
50
.add = acpi_smbus_hc_add,
51
.remove = acpi_smbus_hc_remove,
52
},
53
};
54
55
union acpi_smb_status {
56
u8 raw;
57
struct {
58
u8 status:5;
59
u8 reserved:1;
60
u8 alarm:1;
61
u8 done:1;
62
} fields;
63
};
64
65
enum acpi_smb_status_codes {
66
SMBUS_OK = 0,
67
SMBUS_UNKNOWN_FAILURE = 0x07,
68
SMBUS_DEVICE_ADDRESS_NACK = 0x10,
69
SMBUS_DEVICE_ERROR = 0x11,
70
SMBUS_DEVICE_COMMAND_ACCESS_DENIED = 0x12,
71
SMBUS_UNKNOWN_ERROR = 0x13,
72
SMBUS_DEVICE_ACCESS_DENIED = 0x17,
73
SMBUS_TIMEOUT = 0x18,
74
SMBUS_HOST_UNSUPPORTED_PROTOCOL = 0x19,
75
SMBUS_BUSY = 0x1a,
76
SMBUS_PEC_ERROR = 0x1f,
77
};
78
79
enum acpi_smb_offset {
80
ACPI_SMB_PROTOCOL = 0, /* protocol, PEC */
81
ACPI_SMB_STATUS = 1, /* status */
82
ACPI_SMB_ADDRESS = 2, /* address */
83
ACPI_SMB_COMMAND = 3, /* command */
84
ACPI_SMB_DATA = 4, /* 32 data registers */
85
ACPI_SMB_BLOCK_COUNT = 0x24, /* number of data bytes */
86
ACPI_SMB_ALARM_ADDRESS = 0x25, /* alarm address */
87
ACPI_SMB_ALARM_DATA = 0x26, /* 2 bytes alarm data */
88
};
89
90
static inline int smb_hc_read(struct acpi_smb_hc *hc, u8 address, u8 *data)
91
{
92
return ec_read(hc->offset + address, data);
93
}
94
95
static inline int smb_hc_write(struct acpi_smb_hc *hc, u8 address, u8 data)
96
{
97
return ec_write(hc->offset + address, data);
98
}
99
100
static inline int smb_check_done(struct acpi_smb_hc *hc)
101
{
102
union acpi_smb_status status = {.raw = 0};
103
smb_hc_read(hc, ACPI_SMB_STATUS, &status.raw);
104
return status.fields.done && (status.fields.status == SMBUS_OK);
105
}
106
107
static int wait_transaction_complete(struct acpi_smb_hc *hc, int timeout)
108
{
109
if (wait_event_timeout(hc->wait, smb_check_done(hc),
110
msecs_to_jiffies(timeout)))
111
return 0;
112
/*
113
* After the timeout happens, OS will try to check the status of SMbus.
114
* If the status is what OS expected, it will be regarded as the bogus
115
* timeout.
116
*/
117
if (smb_check_done(hc))
118
return 0;
119
else
120
return -ETIME;
121
}
122
123
static int acpi_smbus_transaction(struct acpi_smb_hc *hc, u8 protocol,
124
u8 address, u8 command, u8 *data, u8 length)
125
{
126
int ret = -EFAULT, i;
127
u8 temp, sz = 0;
128
129
if (!hc) {
130
printk(KERN_ERR PREFIX "host controller is not configured\n");
131
return ret;
132
}
133
134
mutex_lock(&hc->lock);
135
if (smb_hc_read(hc, ACPI_SMB_PROTOCOL, &temp))
136
goto end;
137
if (temp) {
138
ret = -EBUSY;
139
goto end;
140
}
141
smb_hc_write(hc, ACPI_SMB_COMMAND, command);
142
if (!(protocol & 0x01)) {
143
smb_hc_write(hc, ACPI_SMB_BLOCK_COUNT, length);
144
for (i = 0; i < length; ++i)
145
smb_hc_write(hc, ACPI_SMB_DATA + i, data[i]);
146
}
147
smb_hc_write(hc, ACPI_SMB_ADDRESS, address << 1);
148
smb_hc_write(hc, ACPI_SMB_PROTOCOL, protocol);
149
/*
150
* Wait for completion. Save the status code, data size,
151
* and data into the return package (if required by the protocol).
152
*/
153
ret = wait_transaction_complete(hc, 1000);
154
if (ret || !(protocol & 0x01))
155
goto end;
156
switch (protocol) {
157
case SMBUS_RECEIVE_BYTE:
158
case SMBUS_READ_BYTE:
159
sz = 1;
160
break;
161
case SMBUS_READ_WORD:
162
sz = 2;
163
break;
164
case SMBUS_READ_BLOCK:
165
if (smb_hc_read(hc, ACPI_SMB_BLOCK_COUNT, &sz)) {
166
ret = -EFAULT;
167
goto end;
168
}
169
sz &= 0x1f;
170
break;
171
}
172
for (i = 0; i < sz; ++i)
173
smb_hc_read(hc, ACPI_SMB_DATA + i, &data[i]);
174
end:
175
mutex_unlock(&hc->lock);
176
return ret;
177
}
178
179
int acpi_smbus_read(struct acpi_smb_hc *hc, u8 protocol, u8 address,
180
u8 command, u8 *data)
181
{
182
return acpi_smbus_transaction(hc, protocol, address, command, data, 0);
183
}
184
185
EXPORT_SYMBOL_GPL(acpi_smbus_read);
186
187
int acpi_smbus_write(struct acpi_smb_hc *hc, u8 protocol, u8 address,
188
u8 command, u8 *data, u8 length)
189
{
190
return acpi_smbus_transaction(hc, protocol, address, command, data, length);
191
}
192
193
EXPORT_SYMBOL_GPL(acpi_smbus_write);
194
195
int acpi_smbus_register_callback(struct acpi_smb_hc *hc,
196
smbus_alarm_callback callback, void *context)
197
{
198
mutex_lock(&hc->lock);
199
hc->callback = callback;
200
hc->context = context;
201
mutex_unlock(&hc->lock);
202
return 0;
203
}
204
205
EXPORT_SYMBOL_GPL(acpi_smbus_register_callback);
206
207
int acpi_smbus_unregister_callback(struct acpi_smb_hc *hc)
208
{
209
mutex_lock(&hc->lock);
210
hc->callback = NULL;
211
hc->context = NULL;
212
mutex_unlock(&hc->lock);
213
return 0;
214
}
215
216
EXPORT_SYMBOL_GPL(acpi_smbus_unregister_callback);
217
218
static inline void acpi_smbus_callback(void *context)
219
{
220
struct acpi_smb_hc *hc = context;
221
if (hc->callback)
222
hc->callback(hc->context);
223
}
224
225
static int smbus_alarm(void *context)
226
{
227
struct acpi_smb_hc *hc = context;
228
union acpi_smb_status status;
229
u8 address;
230
if (smb_hc_read(hc, ACPI_SMB_STATUS, &status.raw))
231
return 0;
232
/* Check if it is only a completion notify */
233
if (status.fields.done)
234
wake_up(&hc->wait);
235
if (!status.fields.alarm)
236
return 0;
237
mutex_lock(&hc->lock);
238
smb_hc_read(hc, ACPI_SMB_ALARM_ADDRESS, &address);
239
status.fields.alarm = 0;
240
smb_hc_write(hc, ACPI_SMB_STATUS, status.raw);
241
/* We are only interested in events coming from known devices */
242
switch (address >> 1) {
243
case ACPI_SBS_CHARGER:
244
case ACPI_SBS_MANAGER:
245
case ACPI_SBS_BATTERY:
246
acpi_os_execute(OSL_NOTIFY_HANDLER,
247
acpi_smbus_callback, hc);
248
default:;
249
}
250
mutex_unlock(&hc->lock);
251
return 0;
252
}
253
254
typedef int (*acpi_ec_query_func) (void *data);
255
256
extern int acpi_ec_add_query_handler(struct acpi_ec *ec, u8 query_bit,
257
acpi_handle handle, acpi_ec_query_func func,
258
void *data);
259
260
static int acpi_smbus_hc_add(struct acpi_device *device)
261
{
262
int status;
263
unsigned long long val;
264
struct acpi_smb_hc *hc;
265
266
if (!device)
267
return -EINVAL;
268
269
status = acpi_evaluate_integer(device->handle, "_EC", NULL, &val);
270
if (ACPI_FAILURE(status)) {
271
printk(KERN_ERR PREFIX "error obtaining _EC.\n");
272
return -EIO;
273
}
274
275
strcpy(acpi_device_name(device), ACPI_SMB_HC_DEVICE_NAME);
276
strcpy(acpi_device_class(device), ACPI_SMB_HC_CLASS);
277
278
hc = kzalloc(sizeof(struct acpi_smb_hc), GFP_KERNEL);
279
if (!hc)
280
return -ENOMEM;
281
mutex_init(&hc->lock);
282
init_waitqueue_head(&hc->wait);
283
284
hc->ec = acpi_driver_data(device->parent);
285
hc->offset = (val >> 8) & 0xff;
286
hc->query_bit = val & 0xff;
287
device->driver_data = hc;
288
289
acpi_ec_add_query_handler(hc->ec, hc->query_bit, NULL, smbus_alarm, hc);
290
printk(KERN_INFO PREFIX "SBS HC: EC = 0x%p, offset = 0x%0x, query_bit = 0x%0x\n",
291
hc->ec, hc->offset, hc->query_bit);
292
293
return 0;
294
}
295
296
extern void acpi_ec_remove_query_handler(struct acpi_ec *ec, u8 query_bit);
297
298
static int acpi_smbus_hc_remove(struct acpi_device *device, int type)
299
{
300
struct acpi_smb_hc *hc;
301
302
if (!device)
303
return -EINVAL;
304
305
hc = acpi_driver_data(device);
306
acpi_ec_remove_query_handler(hc->ec, hc->query_bit);
307
kfree(hc);
308
device->driver_data = NULL;
309
return 0;
310
}
311
312
static int __init acpi_smb_hc_init(void)
313
{
314
int result;
315
316
result = acpi_bus_register_driver(&acpi_smb_hc_driver);
317
if (result < 0)
318
return -ENODEV;
319
return 0;
320
}
321
322
static void __exit acpi_smb_hc_exit(void)
323
{
324
acpi_bus_unregister_driver(&acpi_smb_hc_driver);
325
}
326
327
module_init(acpi_smb_hc_init);
328
module_exit(acpi_smb_hc_exit);
329
330
MODULE_LICENSE("GPL");
331
MODULE_AUTHOR("Alexey Starikovskiy");
332
MODULE_DESCRIPTION("ACPI SMBus HC driver");
333
334