root/lm-sensors/trunk/kernel/busses/i2c-sis630.c @ 1504

Revision 1504, 11.3 KB (checked in by amalysh, 11 years ago)

(amalysh)
Fixed the typo in sis630_access
Changed sis630_transaction. Now it's 2x faster...

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1/*
2    SIS 630 SMBUS access implementation based on i2c-sis5595.
3
4    Copyright (c) 2002 Alexander Malysh <amalysh@web.de>
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; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, write to the Free Software
18    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19*/
20
21/*
22   Changes:
23   24.08.2002
24        Fixed the typo in sis630_access (Thanks to Mark M. Hoffman)
25        Changed sis630_transaction. Now it's 2x faster (Thanks to Mark M. Hoffman)
26*/
27
28/*
29   TODO:
30     Implement block data write/read
31     Check SMBALT# : why it can't be cleared ????
32     Test on 2.2 kernel
33*/
34/*
35   Supports:
36        SIS 630
37
38   Note: we assume there can only be one device, with one SMBus interface.
39*/
40
41#include <linux/version.h>
42#include <linux/module.h>
43#include <asm/io.h>
44#include <linux/kernel.h>
45#include <linux/pci.h>
46#include <linux/stddef.h>
47#include <linux/sched.h>
48#include <linux/ioport.h>
49#include <linux/i2c.h>
50#include "version.h"
51#include <linux/init.h>
52
53#ifndef I2C_HW_SMBUS_SIS630
54#define I2C_HW_SMBUS_SIS630     0x08
55#endif
56
57/* SIS630 SMBus registers */
58#define SMB_STS     0x80 /* status */
59#define SMB_EN      0x81 /* status enable */
60#define SMB_CNT     0x82
61#define SMBHOST_CNT 0x83
62#define SMB_ADDR    0x84
63#define SMB_CMD     0x85
64#define SMB_PCOUNT  0x86 /* processed count */
65#define SMB_COUNT   0x87
66#define SMB_BYTE    0x88 /* ~0x8F data byte field */
67#define SMBDEV_ADDR 0x90
68#define SMB_DB0     0x91
69#define SMB_DB1     0x92
70#define SMB_SAA     0x93
71
72/* register count for request_region */
73#define SIS630_SMB_IOREGION 20
74
75/* PCI address constants */
76/* acpi base address register  */
77#define SIS630_ACPI_BASE_REG 0x74
78/* bios control register */
79#define SIS630_BIOS_CTL_REG  0x40
80
81/* Other settings */
82#define MAX_TIMEOUT   500
83
84/* SIS630 constants */
85#define SIS630_QUICK      0x00
86#define SIS630_BYTE       0x01
87#define SIS630_BYTE_DATA  0x02
88#define SIS630_WORD_DATA  0x03
89#define SIS630_PCALL      0x04
90#define SIS630_BLOCK_DATA 0x05
91
92
93#ifdef MODULE
94static
95#else
96extern
97#endif
98int __init i2c_sis630_init(void);
99static int __init i2c_sis630_cleanup(void);
100static int sis630_setup(void);
101static s32 sis630_access(struct i2c_adapter *adap, u16 addr,
102                          unsigned short flags, char read_write,
103                          u8 command, int size,
104                          union i2c_smbus_data *data);
105static void sis630_do_pause(unsigned int amount);
106static int sis630_transaction(int size);
107static void sis630_inc(struct i2c_adapter *adapter);
108static void sis630_dec(struct i2c_adapter *adapter);
109static u32 sis630_func(struct i2c_adapter *adapter);
110static u8 sis630_read(u8 reg);
111static void sis630_write(u8 reg, u8 data);
112
113
114static struct i2c_algorithm smbus_algorithm = {
115        /* name */ "Non-I2C SMBus adapter",
116        /* id */ I2C_ALGO_SMBUS,
117        /* master_xfer */ NULL,
118        /* smbus_access */ sis630_access,
119        /* slave_send */ NULL,
120        /* slave_rcv */ NULL,
121        /* algo_control */ NULL,
122        /* functionality */ sis630_func,
123};
124
125static struct i2c_adapter sis630_adapter = {
126        "unset",
127        I2C_ALGO_SMBUS | I2C_HW_SMBUS_SIS630,
128        &smbus_algorithm,
129        NULL,
130        sis630_inc,
131        sis630_dec,
132        NULL,
133        NULL,
134};
135
136static unsigned short acpi_base = 0;
137static int __initdata sis630_initialized;
138
139u8 sis630_read(u8 reg) {
140        return inb(acpi_base + reg);
141}
142
143void sis630_write(u8 reg, u8 data) {
144        outb(data, acpi_base + reg);
145}
146
147/* Internally used pause function */
148void sis630_do_pause(unsigned int amount)
149{
150        current->state = TASK_INTERRUPTIBLE;
151        schedule_timeout(amount);
152}
153
154int sis630_transaction(int size) {
155        int temp;
156        int result = 0;
157        int timeout = 0;
158
159        /*
160          Make sure the SMBus host is ready to start transmitting.
161        */
162        if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
163#ifdef DEBUG
164                printk(KERN_DEBUG "i2c-sis630.o: SMBus busy (%02x). "
165                        "Resetting...\n",temp);
166#endif
167                /* kill smbus transaction */
168                sis630_write(SMBHOST_CNT, 0x02);
169
170                if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) {
171#ifdef DEBUG
172                        printk(KERN_DEBUG "i2c-sis630.o: Failed! (%02x)\n",
173                                temp);
174#endif
175                        return -1;
176                } else {
177#ifdef DEBUG
178                        printk(KERN_DEBUG "i2c-sis630.o: Successfull!\n");
179#endif
180                }
181        }
182
183        /* disable timeout interrupt and set clock to 56KHz */
184        sis630_write(SMB_CNT, 0x20);
185
186        /* clear all sticky bits */
187        temp = sis630_read(SMB_STS);
188        sis630_write(SMB_STS, temp & 0x1e);
189
190        /* start the transaction by setting bit 4 and size */
191        sis630_write(SMBHOST_CNT,0x10 | (size & 0x07));
192
193        /* We will always wait for a fraction of a second! */
194        do {
195                sis630_do_pause(1);
196                temp = sis630_read(SMB_STS);
197        } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT));
198
199        /* If the SMBus is still busy, we give up */
200        if (timeout >= MAX_TIMEOUT) {
201#ifdef DEBUG
202                printk(KERN_DEBUG "i2c-sis630.o: SMBus Timeout!\n");
203#endif
204                result = -1;
205        }
206
207        if (temp & 0x02) {
208                result = -1;
209#ifdef DEBUG
210                printk(KERN_DEBUG "i2c-sis630.o: Error: Failed bus "
211                        "transaction\n");
212#endif
213        }
214
215        if (temp & 0x04) {
216                result = -1;
217                printk(KERN_ERR "i2c-sis630.o: Bus collision! "
218                        "SMBus may be locked until next hard reset (or not...)\n");
219                /* TBD: Datasheet say:
220                   the software should clear this bit and restart SMBUS operation
221                */
222        }
223
224        /* clear all status "sticky" bits */
225        sis630_write(SMB_STS, temp);
226
227        return result;
228}
229
230/* Return -1 on error. */
231s32 sis630_access(struct i2c_adapter * adap, u16 addr,
232                   unsigned short flags, char read_write,
233                   u8 command, int size, union i2c_smbus_data * data) {
234
235        switch (size) {
236                case I2C_SMBUS_QUICK:
237                        sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
238                        size = SIS630_QUICK;
239                        break;
240                case I2C_SMBUS_BYTE:
241                        sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
242                        if (read_write == I2C_SMBUS_WRITE)
243                                sis630_write(SMB_CMD, command);
244                        size = SIS630_BYTE;
245                        break;
246                case I2C_SMBUS_BYTE_DATA:
247                        sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01));
248                        sis630_write(SMB_CMD, command);
249                        if (read_write == I2C_SMBUS_WRITE)
250                                sis630_write(SMB_BYTE, data->byte);
251                        size = SIS630_BYTE_DATA;
252                        break;
253                case I2C_SMBUS_PROC_CALL:
254                case I2C_SMBUS_WORD_DATA:
255                        sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
256                        sis630_write(SMB_CMD, command);
257                        if (read_write == I2C_SMBUS_WRITE) {
258                                sis630_write(SMB_BYTE, data->word & 0xff);
259                                sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8);
260                        }
261                        size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA);
262                        break;
263                case I2C_SMBUS_BLOCK_DATA:
264/* it's not implemented yet, but comming soon */
265#if 0
266                        sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01));
267                        sis630_write(SMB_CMD, command);
268                        size = SIS630_BLOCK_DATA;
269#endif
270                default:
271                        printk("Unsupported I2C size\n");
272                        return -1;
273                        break;
274        }
275
276
277        if (sis630_transaction(size))
278                return -1;
279
280        if ((size != I2C_SMBUS_PROC_CALL) &&
281                ((read_write == I2C_SMBUS_WRITE) || (size == I2C_SMBUS_QUICK))) {
282                return 0;
283        }
284
285        switch(size) {
286                case SIS630_BYTE:
287                case SIS630_BYTE_DATA:
288                        data->byte = sis630_read(SMB_BYTE);
289                        break;
290                case SIS630_PCALL:
291                case SIS630_WORD_DATA:
292                        data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8);
293                        break;
294                default:
295                        return -1;
296                        break;
297        }
298
299        return 0;
300}
301
302void sis630_inc(struct i2c_adapter *adapter) {
303        MOD_INC_USE_COUNT;
304}
305
306void sis630_dec(struct i2c_adapter *adapter) {
307        MOD_DEC_USE_COUNT;
308}
309
310u32 sis630_func(struct i2c_adapter *adapter) {
311        return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA |
312                I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL;
313}
314
315int sis630_setup(void) {
316        unsigned char b;
317        struct pci_dev *sis630_dev = NULL;
318
319        /* First check whether we can access PCI at all */
320        if (pci_present() == 0) {
321                printk("i2c-sis630.o: Error: No PCI-bus found!\n");
322                return -ENODEV;
323        }
324
325        /* Look for the SIS630 */
326        if (!(sis630_dev = pci_find_device(PCI_VENDOR_ID_SI,
327                                            PCI_DEVICE_ID_SI_503,
328                                            sis630_dev))) {
329                printk(KERN_ERR "i2c-sis630.o: Error: Can't detect SIS630!\n");
330                return -ENODEV;
331        }
332        if (NULL == pci_find_device(PCI_VENDOR_ID_SI,PCI_DEVICE_ID_SI_630,NULL)) {
333                printk(KERN_ERR "i2c-sis630.o: Error: Can't detect SIS630!\n");
334                return -ENODEV;
335        }
336
337        /*
338           Enable ACPI first , so we can accsess reg 74-75
339           in acpi io space and read acpi base addr
340        */
341        if (PCIBIOS_SUCCESSFUL !=
342            pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) {
343                printk(KERN_ERR "i2c-sis630.o: Error: Can't read bios ctl reg\n");
344                return -ENODEV;
345        }
346        /* if ACPI already anbled , do nothing */
347        if (!(b & 0x80) &&
348            PCIBIOS_SUCCESSFUL !=
349            pci_write_config_byte(sis630_dev,SIS630_BIOS_CTL_REG,b|0x80)) {
350                printk(KERN_ERR "i2c-sis630.o: Error: Can't enable ACPI\n");
351                return -ENODEV;
352        }
353        /* Determine the ACPI base address */
354        if (PCIBIOS_SUCCESSFUL !=
355            pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) {
356                printk(KERN_ERR "i2c-sis630.o: Error: Can't determine ACPI base address\n");
357                return -ENODEV;
358        }
359
360#ifdef DEBUG
361        printk(KERN_DEBUG "i2c-sis630.o: ACPI base at 0x%04x\n", acpi_base);
362#endif
363
364        /* Everything is happy, let's grab the memory and set things up. */
365        if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, "sis630-smbus")){
366                printk(KERN_ERR "i2c-sis630.o: SMBus registers 0x%04x-0x%04x "
367                        "already in use!\n",acpi_base + SMB_STS, acpi_base + SMB_SAA);
368                return -ENODEV;
369        }
370
371        return 0;
372}
373
374int __init i2c_sis630_init(void) {
375        int res;
376        printk("i2c-sis630.o version %s (%s)\n", LM_VERSION, LM_DATE);
377
378        if (sis630_initialized) {
379                printk(KERN_ERR "i2c-sis630.o: Oops, sis630_init called a second time!\n");
380                return -EBUSY;
381        }
382
383        sis630_initialized = 0;
384        if ((res = sis630_setup())) {
385                printk(KERN_ERR "i2c-sis630.o: SIS630 not detected, module not inserted.\n");
386                i2c_sis630_cleanup();
387                return res;
388        }
389        sis630_initialized++;
390        sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x",
391                acpi_base + SMB_STS);
392        if ((res = i2c_add_adapter(&sis630_adapter))) {
393                printk(KERN_ERR "i2c-sis630.o: Adapter registration failed, "
394                        "module not inserted.\n");
395                i2c_sis630_cleanup();
396                return res;
397        }
398        sis630_initialized++;
399        printk(KERN_INFO "i2c-sis630.o: SIS630 bus detected and initialized\n");
400
401        return 0;
402}
403
404int __init i2c_sis630_cleanup(void) {
405        int res;
406        if (sis630_initialized >= 2) {
407                if ((res = i2c_del_adapter(&sis630_adapter))) {
408                        printk(KERN_ERR "i2c-sis630.o: i2c_del_adapter failed, module not"
409                                "removed\n");
410                        return res;
411                } else
412                        sis630_initialized--;
413        }
414        if (sis630_initialized >= 1) {
415                release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION);
416                sis630_initialized--;
417        }
418        return 0;
419}
420
421
422EXPORT_NO_SYMBOLS;
423
424#ifdef MODULE_LICENSE
425MODULE_LICENSE("GPL");
426#endif
427
428#ifdef MODULE
429MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>");
430MODULE_DESCRIPTION("SIS630 SMBus driver");
431
432int init_module(void) {
433        return i2c_sis630_init();
434}
435
436int cleanup_module(void) {
437        return i2c_sis630_cleanup();
438}
439
440#endif  /* MODULE */
Note: See TracBrowser for help on using the browser.