blob: 72c29a650b22c44d7954e5c392bb16b8949ac5d7 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 i2c-sis630.c - Part of lm_sensors, Linux kernel modules for hardware
3 monitoring
4
5 Copyright (c) 2002,2003 Alexander Malysh <amalysh@web.de>
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20*/
21
22/*
23 Changes:
24 24.08.2002
25 Fixed the typo in sis630_access (Thanks to Mark M. Hoffman)
26 Changed sis630_transaction.(Thanks to Mark M. Hoffman)
27 18.09.2002
28 Added SIS730 as supported.
29 21.09.2002
30 Added high_clock module option.If this option is set
31 used Host Master Clock 56KHz (default 14KHz).For now we save old Host
32 Master Clock and after transaction completed restore (otherwise
33 it's confuse BIOS and hung Machine).
34 24.09.2002
35 Fixed typo in sis630_access
36 Fixed logical error by restoring of Host Master Clock
37 31.07.2003
38 Added block data read/write support.
39*/
40
41/*
42 Status: beta
43
44 Supports:
45 SIS 630
46 SIS 730
47
48 Note: we assume there can only be one device, with one SMBus interface.
49*/
50
Linus Torvalds1da177e2005-04-16 15:20:36 -070051#include <linux/kernel.h>
52#include <linux/module.h>
53#include <linux/delay.h>
54#include <linux/pci.h>
55#include <linux/ioport.h>
56#include <linux/init.h>
57#include <linux/i2c.h>
Jean Delvare54fb4a052008-07-14 22:38:33 +020058#include <linux/acpi.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070059#include <asm/io.h>
60
61/* SIS630 SMBus registers */
62#define SMB_STS 0x80 /* status */
63#define SMB_EN 0x81 /* status enable */
64#define SMB_CNT 0x82
65#define SMBHOST_CNT 0x83
66#define SMB_ADDR 0x84
67#define SMB_CMD 0x85
68#define SMB_PCOUNT 0x86 /* processed count */
69#define SMB_COUNT 0x87
70#define SMB_BYTE 0x88 /* ~0x8F data byte field */
71#define SMBDEV_ADDR 0x90
72#define SMB_DB0 0x91
73#define SMB_DB1 0x92
74#define SMB_SAA 0x93
75
76/* register count for request_region */
77#define SIS630_SMB_IOREGION 20
78
79/* PCI address constants */
80/* acpi base address register */
81#define SIS630_ACPI_BASE_REG 0x74
82/* bios control register */
83#define SIS630_BIOS_CTL_REG 0x40
84
85/* Other settings */
86#define MAX_TIMEOUT 500
87
88/* SIS630 constants */
89#define SIS630_QUICK 0x00
90#define SIS630_BYTE 0x01
91#define SIS630_BYTE_DATA 0x02
92#define SIS630_WORD_DATA 0x03
93#define SIS630_PCALL 0x04
94#define SIS630_BLOCK_DATA 0x05
95
Jean Delvared6072f82005-09-25 16:37:04 +020096static struct pci_driver sis630_driver;
97
Linus Torvalds1da177e2005-04-16 15:20:36 -070098/* insmod parameters */
99static int high_clock;
100static int force;
101module_param(high_clock, bool, 0);
102MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz).");
103module_param(force, bool, 0);
104MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!");
105
106/* acpi base address */
Jean Delvare60507092005-09-25 16:23:07 +0200107static unsigned short acpi_base;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700108
109/* supported chips */
110static int supported[] = {
111 PCI_DEVICE_ID_SI_630,
112 PCI_DEVICE_ID_SI_730,
113 0 /* terminates the list */
114};
115
116static inline u8 sis630_read(u8 reg)
117{
118 return inb(acpi_base + reg);
119}
120
121static inline void sis630_write(u8 reg, u8 data)
122{
123 outb(data, acpi_base + reg);
124}
125
126static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock)
127{
128 int temp;
129
130 /* Make sure the SMBus host is ready to start transmitting. */
131 if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
132 dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp);
133 /* kill smbus transaction */
134 sis630_write(SMBHOST_CNT, 0x20);
135
136 if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
137 dev_dbg(&adap->dev, "Failed! (%02x)\n", temp);
David Brownell97140342008-07-14 22:38:25 +0200138 return -EBUSY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700139 } else {
Jean Delvarec5d21b72008-04-29 23:11:37 +0200140 dev_dbg(&adap->dev, "Successful!\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700141 }
142 }
143
144 /* save old clock, so we can prevent machine for hung */
145 *oldclock = sis630_read(SMB_CNT);
146
147 dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock);
148
149 /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */
150 if (high_clock)
151 sis630_write(SMB_CNT, 0x20);
152 else
153 sis630_write(SMB_CNT, (*oldclock & ~0x40));
154
155 /* clear all sticky bits */
156 temp = sis630_read(SMB_STS);
157 sis630_write(SMB_STS, temp & 0x1e);
158
159 /* start the transaction by setting bit 4 and size */
160 sis630_write(SMBHOST_CNT,0x10 | (size & 0x07));
161
162 return 0;
163}
164
165static int sis630_transaction_wait(struct i2c_adapter *adap, int size)
166{
167 int temp, result = 0, timeout = 0;
168
169 /* We will always wait for a fraction of a second! */
170 do {
171 msleep(1);
172 temp = sis630_read(SMB_STS);
173 /* check if block transmitted */
174 if (size == SIS630_BLOCK_DATA && (temp & 0x10))
175 break;
176 } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
177
178 /* If the SMBus is still busy, we give up */
179 if (timeout >= MAX_TIMEOUT) {
180 dev_dbg(&adap->dev, "SMBus Timeout!\n");
David Brownell97140342008-07-14 22:38:25 +0200181 result = -ETIMEDOUT;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700182 }
183
184 if (temp & 0x02) {
185 dev_dbg(&adap->dev, "Error: Failed bus transaction\n");
David Brownell97140342008-07-14 22:38:25 +0200186 result = -ENXIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700187 }
188
189 if (temp & 0x04) {
190 dev_err(&adap->dev, "Bus collision!\n");
David Brownell97140342008-07-14 22:38:25 +0200191 result = -EIO;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700192 /*
193 TBD: Datasheet say:
194 the software should clear this bit and restart SMBUS operation.
195 Should we do it or user start request again?
196 */
197 }
198
199 return result;
200}
201
202static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock)
203{
204 int temp = 0;
205
206 /* clear all status "sticky" bits */
207 sis630_write(SMB_STS, temp);
208
209 dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT));
210
211 /*
212 * restore old Host Master Clock if high_clock is set
213 * and oldclock was not 56KHz
214 */
215 if (high_clock && !(oldclock & 0x20))
216 sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20));
217
218 dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT));
219}
220
221static int sis630_transaction(struct i2c_adapter *adap, int size)
222{
223 int result = 0;
224 u8 oldclock = 0;
225
226 result = sis630_transaction_start(adap, size, &oldclock);
227 if (!result) {
228 result = sis630_transaction_wait(adap, size);
229 sis630_transaction_end(adap, oldclock);
230 }
231
232 return result;
233}
234
235static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write)
236{
237 int i, len = 0, rc = 0;
238 u8 oldclock = 0;
239
240 if (read_write == I2C_SMBUS_WRITE) {
241 len = data->block[0];
242 if (len < 0)
243 len = 0;
244 else if (len > 32)
245 len = 32;
246 sis630_write(SMB_COUNT, len);
247 for (i=1; i <= len; i++) {
248 dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]);
249 /* set data */
250 sis630_write(SMB_BYTE+(i-1)%8, data->block[i]);
251 if (i==8 || (len<8 && i==len)) {
252 dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i);
253 /* first transaction */
David Brownell97140342008-07-14 22:38:25 +0200254 rc = sis630_transaction_start(adap,
255 SIS630_BLOCK_DATA, &oldclock);
256 if (rc)
257 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700258 }
259 else if ((i-1)%8 == 7 || i==len) {
260 dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i);
261 if (i>8) {
262 dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
263 /*
264 If this is not first transaction,
265 we must clear sticky bit.
266 clear SMBARY_STS
267 */
268 sis630_write(SMB_STS,0x10);
269 }
David Brownell97140342008-07-14 22:38:25 +0200270 rc = sis630_transaction_wait(adap,
271 SIS630_BLOCK_DATA);
272 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700273 dev_dbg(&adap->dev, "trans_wait failed\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700274 break;
275 }
276 }
277 }
278 }
279 else {
280 /* read request */
281 data->block[0] = len = 0;
David Brownell97140342008-07-14 22:38:25 +0200282 rc = sis630_transaction_start(adap,
283 SIS630_BLOCK_DATA, &oldclock);
284 if (rc)
285 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700286 do {
David Brownell97140342008-07-14 22:38:25 +0200287 rc = sis630_transaction_wait(adap, SIS630_BLOCK_DATA);
288 if (rc) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289 dev_dbg(&adap->dev, "trans_wait failed\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700290 break;
291 }
292 /* if this first transaction then read byte count */
293 if (len == 0)
294 data->block[0] = sis630_read(SMB_COUNT);
295
296 /* just to be sure */
297 if (data->block[0] > 32)
298 data->block[0] = 32;
299
300 dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]);
301
302 for (i=0; i < 8 && len < data->block[0]; i++,len++) {
303 dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len);
304 data->block[len+1] = sis630_read(SMB_BYTE+i);
305 }
306
307 dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i);
308
309 /* clear SMBARY_STS */
310 sis630_write(SMB_STS,0x10);
311 } while(len < data->block[0]);
312 }
313
314 sis630_transaction_end(adap, oldclock);
315
316 return rc;
317}
318
David Brownell97140342008-07-14 22:38:25 +0200319/* Return negative errno on error. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700320static s32 sis630_access(struct i2c_adapter *adap, u16 addr,
321 unsigned short flags, char read_write,
322 u8 command, int size, union i2c_smbus_data *data)
323{
David Brownell97140342008-07-14 22:38:25 +0200324 int status;
325
Linus Torvalds1da177e2005-04-16 15:20:36 -0700326 switch (size) {
327 case I2C_SMBUS_QUICK:
328 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
329 size = SIS630_QUICK;
330 break;
331 case I2C_SMBUS_BYTE:
332 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
333 if (read_write == I2C_SMBUS_WRITE)
334 sis630_write(SMB_CMD, command);
335 size = SIS630_BYTE;
336 break;
337 case I2C_SMBUS_BYTE_DATA:
338 sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
339 sis630_write(SMB_CMD, command);
340 if (read_write == I2C_SMBUS_WRITE)
341 sis630_write(SMB_BYTE, data->byte);
342 size = SIS630_BYTE_DATA;
343 break;
344 case I2C_SMBUS_PROC_CALL:
345 case I2C_SMBUS_WORD_DATA:
346 sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
347 sis630_write(SMB_CMD, command);
348 if (read_write == I2C_SMBUS_WRITE) {
349 sis630_write(SMB_BYTE, data->word & 0xff);
350 sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8);
351 }
352 size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA);
353 break;
354 case I2C_SMBUS_BLOCK_DATA:
355 sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
356 sis630_write(SMB_CMD, command);
357 size = SIS630_BLOCK_DATA;
358 return sis630_block_data(adap, data, read_write);
359 default:
Jean Delvareac7fc4f2008-07-14 22:38:25 +0200360 dev_warn(&adap->dev, "Unsupported transaction %d\n",
361 size);
David Brownell97140342008-07-14 22:38:25 +0200362 return -EOPNOTSUPP;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700363 }
364
David Brownell97140342008-07-14 22:38:25 +0200365 status = sis630_transaction(adap, size);
366 if (status)
367 return status;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700368
369 if ((size != SIS630_PCALL) &&
370 ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) {
371 return 0;
372 }
373
374 switch(size) {
375 case SIS630_BYTE:
376 case SIS630_BYTE_DATA:
377 data->byte = sis630_read(SMB_BYTE);
378 break;
379 case SIS630_PCALL:
380 case SIS630_WORD_DATA:
381 data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8);
382 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700383 }
384
385 return 0;
386}
387
388static u32 sis630_func(struct i2c_adapter *adapter)
389{
390 return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
391 I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL |
392 I2C_FUNC_SMBUS_BLOCK_DATA;
393}
394
395static int sis630_setup(struct pci_dev *sis630_dev)
396{
397 unsigned char b;
398 struct pci_dev *dummy = NULL;
399 int retval = -ENODEV, i;
400
401 /* check for supported SiS devices */
402 for (i=0; supported[i] > 0 ; i++) {
403 if ((dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy)))
404 break; /* found */
405 }
406
407 if (dummy) {
408 pci_dev_put(dummy);
409 }
410 else if (force) {
411 dev_err(&sis630_dev->dev, "WARNING: Can't detect SIS630 compatible device, but "
412 "loading because of force option enabled\n");
413 }
414 else {
415 return -ENODEV;
416 }
417
418 /*
419 Enable ACPI first , so we can accsess reg 74-75
420 in acpi io space and read acpi base addr
421 */
422 if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) {
423 dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n");
424 goto exit;
425 }
426 /* if ACPI already enabled , do nothing */
427 if (!(b & 0x80) &&
428 pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) {
429 dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n");
430 goto exit;
431 }
432
433 /* Determine the ACPI base address */
434 if (pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) {
435 dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n");
436 goto exit;
437 }
438
439 dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base);
440
Jean Delvare54fb4a052008-07-14 22:38:33 +0200441 retval = acpi_check_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION,
442 sis630_driver.name);
443 if (retval)
444 goto exit;
445
Linus Torvalds1da177e2005-04-16 15:20:36 -0700446 /* Everything is happy, let's grab the memory and set things up. */
Jean Delvared6072f82005-09-25 16:37:04 +0200447 if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION,
448 sis630_driver.name)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700449 dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already "
450 "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA);
451 goto exit;
452 }
453
454 retval = 0;
455
456exit:
457 if (retval)
458 acpi_base = 0;
459 return retval;
460}
461
462
Jean Delvare8f9082c2006-09-03 22:39:46 +0200463static const struct i2c_algorithm smbus_algorithm = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700464 .smbus_xfer = sis630_access,
465 .functionality = sis630_func,
466};
467
468static struct i2c_adapter sis630_adapter = {
469 .owner = THIS_MODULE,
Stephen Hemminger9ace5552007-02-13 22:09:01 +0100470 .id = I2C_HW_SMBUS_SIS630,
Jean Delvare3401b2f2008-07-14 22:38:29 +0200471 .class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700472 .algo = &smbus_algorithm,
473};
474
475static struct pci_device_id sis630_ids[] __devinitdata = {
476 { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) },
477 { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) },
478 { 0, }
479};
480
481MODULE_DEVICE_TABLE (pci, sis630_ids);
482
483static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id)
484{
485 if (sis630_setup(dev)) {
486 dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n");
487 return -ENODEV;
488 }
489
Robert P. J. Day405ae7d2007-02-17 19:13:42 +0100490 /* set up the sysfs linkage to our parent device */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700491 sis630_adapter.dev.parent = &dev->dev;
492
493 sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x",
494 acpi_base + SMB_STS);
495
496 return i2c_add_adapter(&sis630_adapter);
497}
498
499static void __devexit sis630_remove(struct pci_dev *dev)
500{
501 if (acpi_base) {
502 i2c_del_adapter(&sis630_adapter);
503 release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION);
504 acpi_base = 0;
505 }
506}
507
508
509static struct pci_driver sis630_driver = {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700510 .name = "sis630_smbus",
511 .id_table = sis630_ids,
512 .probe = sis630_probe,
513 .remove = __devexit_p(sis630_remove),
514};
515
516static int __init i2c_sis630_init(void)
517{
518 return pci_register_driver(&sis630_driver);
519}
520
521
522static void __exit i2c_sis630_exit(void)
523{
524 pci_unregister_driver(&sis630_driver);
525}
526
527
528MODULE_LICENSE("GPL");
529MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>");
530MODULE_DESCRIPTION("SIS630 SMBus driver");
531
532module_init(i2c_sis630_init);
533module_exit(i2c_sis630_exit);