Blame view

drivers/acpi/sbshc.c 7.2 KB
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
1
2
3
4
5
6
7
8
9
  /*
   * 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.
   */
8b48463f8   Lv Zheng   ACPI: Clean up in...
10
  #include <linux/acpi.h>
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
11
  #include <linux/wait.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
12
  #include <linux/slab.h>
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
13
  #include <linux/delay.h>
cc4b859c7   Paul Gortmaker   acpi: add module....
14
  #include <linux/module.h>
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
15
16
  #include <linux/interrupt.h>
  #include "sbshc.h"
a192a9580   Len Brown   ACPI: Move defini...
17
  #define PREFIX "ACPI: "
97c227cb5   Dan Carpenter   sbshc: acpi_devic...
18
  #define ACPI_SMB_HC_CLASS	"smbus_host_ctl"
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
19
20
21
22
23
24
25
26
27
28
  #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;
add68d6aa   Chris Bainbridge   ACPI / SMBus: Fix...
29
  	bool done;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
30
31
32
  };
  
  static int acpi_smbus_hc_add(struct acpi_device *device);
51fac8388   Rafael J. Wysocki   ACPI: Remove usel...
33
  static int acpi_smbus_hc_remove(struct acpi_device *device);
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
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
  
  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);
  }
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
97
98
  static int wait_transaction_complete(struct acpi_smb_hc *hc, int timeout)
  {
add68d6aa   Chris Bainbridge   ACPI / SMBus: Fix...
99
  	if (wait_event_timeout(hc->wait, hc->done, msecs_to_jiffies(timeout)))
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
100
  		return 0;
add68d6aa   Chris Bainbridge   ACPI / SMBus: Fix...
101
  	return -ETIME;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
102
  }
e5685b9d3   Adrian Bunk   ACPI: misc cleanups
103
104
  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 ...
105
106
107
  {
  	int ret = -EFAULT, i;
  	u8 temp, sz = 0;
bbafbecb2   Alexey Starikovskiy   ACPI: SBS: Host c...
108
109
110
111
112
  	if (!hc) {
  		printk(KERN_ERR PREFIX "host controller is not configured
  ");
  		return ret;
  	}
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
113
  	mutex_lock(&hc->lock);
add68d6aa   Chris Bainbridge   ACPI / SMBus: Fix...
114
  	hc->done = false;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
115
116
117
118
119
120
121
  	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 ...
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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
  	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...
197
  static inline void acpi_smbus_callback(void *context)
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
198
199
  {
  	struct acpi_smb_hc *hc = context;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
200
201
202
203
204
205
206
207
  	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...
208
  	u8 address;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
209
210
211
  	if (smb_hc_read(hc, ACPI_SMB_STATUS, &status.raw))
  		return 0;
  	/* Check if it is only a completion notify */
add68d6aa   Chris Bainbridge   ACPI / SMBus: Fix...
212
213
  	if (status.fields.done && status.fields.status == SMBUS_OK) {
  		hc->done = true;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
214
  		wake_up(&hc->wait);
add68d6aa   Chris Bainbridge   ACPI / SMBus: Fix...
215
  	}
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
216
217
218
  	if (!status.fields.alarm)
  		return 0;
  	mutex_lock(&hc->lock);
c2d00f2d1   Alexey Starikovskiy   ACPI: SBS: Ignore...
219
  	smb_hc_read(hc, ACPI_SMB_ALARM_ADDRESS, &address);
09f1fb41a   Alexey Starikovskiy   ACPI: SBS: Reset ...
220
  	status.fields.alarm = 0;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
221
  	smb_hc_write(hc, ACPI_SMB_STATUS, status.raw);
c2d00f2d1   Alexey Starikovskiy   ACPI: SBS: Ignore...
222
223
224
225
226
  	/* 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...
227
  			acpi_os_execute(OSL_NOTIFY_HANDLER,
c2d00f2d1   Alexey Starikovskiy   ACPI: SBS: Ignore...
228
229
230
  					acpi_smbus_callback, hc);
  		default:;
  	}
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
231
232
233
234
235
236
237
238
239
240
241
242
243
  	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...
244
  	unsigned long long val;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
  	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...
269
  	device->driver_data = hc;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
270
271
272
273
274
275
276
277
278
279
  
  	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);
51fac8388   Rafael J. Wysocki   ACPI: Remove usel...
280
  static int acpi_smbus_hc_remove(struct acpi_device *device)
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
281
282
283
284
285
286
287
288
289
  {
  	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...
290
  	device->driver_data = NULL;
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
291
292
  	return 0;
  }
e84a239e7   Mika Westerberg   ACPI/sbshc: conve...
293
  module_acpi_driver(acpi_smb_hc_driver);
91087dfa5   Alexey Starikovskiy   ACPI: SBS: Split ...
294
295
296
297
  
  MODULE_LICENSE("GPL");
  MODULE_AUTHOR("Alexey Starikovskiy");
  MODULE_DESCRIPTION("ACPI SMBus HC driver");