Blame view

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