diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/i2c/busses/i2c-sis630.c |
Linux-2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/i2c/busses/i2c-sis630.c')
-rw-r--r-- | drivers/i2c/busses/i2c-sis630.c | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/drivers/i2c/busses/i2c-sis630.c b/drivers/i2c/busses/i2c-sis630.c new file mode 100644 index 000000000000..58df63df1540 --- /dev/null +++ b/drivers/i2c/busses/i2c-sis630.c | |||
@@ -0,0 +1,523 @@ | |||
1 | /* | ||
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 | |||
51 | #include <linux/config.h> | ||
52 | #include <linux/kernel.h> | ||
53 | #include <linux/module.h> | ||
54 | #include <linux/delay.h> | ||
55 | #include <linux/pci.h> | ||
56 | #include <linux/ioport.h> | ||
57 | #include <linux/init.h> | ||
58 | #include <linux/i2c.h> | ||
59 | #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 | |||
96 | /* insmod parameters */ | ||
97 | static int high_clock; | ||
98 | static int force; | ||
99 | module_param(high_clock, bool, 0); | ||
100 | MODULE_PARM_DESC(high_clock, "Set Host Master Clock to 56KHz (default 14KHz)."); | ||
101 | module_param(force, bool, 0); | ||
102 | MODULE_PARM_DESC(force, "Forcibly enable the SIS630. DANGEROUS!"); | ||
103 | |||
104 | /* acpi base address */ | ||
105 | static unsigned short acpi_base = 0; | ||
106 | |||
107 | /* supported chips */ | ||
108 | static int supported[] = { | ||
109 | PCI_DEVICE_ID_SI_630, | ||
110 | PCI_DEVICE_ID_SI_730, | ||
111 | 0 /* terminates the list */ | ||
112 | }; | ||
113 | |||
114 | static inline u8 sis630_read(u8 reg) | ||
115 | { | ||
116 | return inb(acpi_base + reg); | ||
117 | } | ||
118 | |||
119 | static inline void sis630_write(u8 reg, u8 data) | ||
120 | { | ||
121 | outb(data, acpi_base + reg); | ||
122 | } | ||
123 | |||
124 | static int sis630_transaction_start(struct i2c_adapter *adap, int size, u8 *oldclock) | ||
125 | { | ||
126 | int temp; | ||
127 | |||
128 | /* Make sure the SMBus host is ready to start transmitting. */ | ||
129 | if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { | ||
130 | dev_dbg(&adap->dev, "SMBus busy (%02x).Resetting...\n",temp); | ||
131 | /* kill smbus transaction */ | ||
132 | sis630_write(SMBHOST_CNT, 0x20); | ||
133 | |||
134 | if ((temp = sis630_read(SMB_CNT) & 0x03) != 0x00) { | ||
135 | dev_dbg(&adap->dev, "Failed! (%02x)\n", temp); | ||
136 | return -1; | ||
137 | } else { | ||
138 | dev_dbg(&adap->dev, "Successfull!\n"); | ||
139 | } | ||
140 | } | ||
141 | |||
142 | /* save old clock, so we can prevent machine for hung */ | ||
143 | *oldclock = sis630_read(SMB_CNT); | ||
144 | |||
145 | dev_dbg(&adap->dev, "saved clock 0x%02x\n", *oldclock); | ||
146 | |||
147 | /* disable timeout interrupt , set Host Master Clock to 56KHz if requested */ | ||
148 | if (high_clock) | ||
149 | sis630_write(SMB_CNT, 0x20); | ||
150 | else | ||
151 | sis630_write(SMB_CNT, (*oldclock & ~0x40)); | ||
152 | |||
153 | /* clear all sticky bits */ | ||
154 | temp = sis630_read(SMB_STS); | ||
155 | sis630_write(SMB_STS, temp & 0x1e); | ||
156 | |||
157 | /* start the transaction by setting bit 4 and size */ | ||
158 | sis630_write(SMBHOST_CNT,0x10 | (size & 0x07)); | ||
159 | |||
160 | return 0; | ||
161 | } | ||
162 | |||
163 | static int sis630_transaction_wait(struct i2c_adapter *adap, int size) | ||
164 | { | ||
165 | int temp, result = 0, timeout = 0; | ||
166 | |||
167 | /* We will always wait for a fraction of a second! */ | ||
168 | do { | ||
169 | msleep(1); | ||
170 | temp = sis630_read(SMB_STS); | ||
171 | /* check if block transmitted */ | ||
172 | if (size == SIS630_BLOCK_DATA && (temp & 0x10)) | ||
173 | break; | ||
174 | } while (!(temp & 0x0e) && (timeout++ < MAX_TIMEOUT)); | ||
175 | |||
176 | /* If the SMBus is still busy, we give up */ | ||
177 | if (timeout >= MAX_TIMEOUT) { | ||
178 | dev_dbg(&adap->dev, "SMBus Timeout!\n"); | ||
179 | result = -1; | ||
180 | } | ||
181 | |||
182 | if (temp & 0x02) { | ||
183 | dev_dbg(&adap->dev, "Error: Failed bus transaction\n"); | ||
184 | result = -1; | ||
185 | } | ||
186 | |||
187 | if (temp & 0x04) { | ||
188 | dev_err(&adap->dev, "Bus collision!\n"); | ||
189 | result = -1; | ||
190 | /* | ||
191 | TBD: Datasheet say: | ||
192 | the software should clear this bit and restart SMBUS operation. | ||
193 | Should we do it or user start request again? | ||
194 | */ | ||
195 | } | ||
196 | |||
197 | return result; | ||
198 | } | ||
199 | |||
200 | static void sis630_transaction_end(struct i2c_adapter *adap, u8 oldclock) | ||
201 | { | ||
202 | int temp = 0; | ||
203 | |||
204 | /* clear all status "sticky" bits */ | ||
205 | sis630_write(SMB_STS, temp); | ||
206 | |||
207 | dev_dbg(&adap->dev, "SMB_CNT before clock restore 0x%02x\n", sis630_read(SMB_CNT)); | ||
208 | |||
209 | /* | ||
210 | * restore old Host Master Clock if high_clock is set | ||
211 | * and oldclock was not 56KHz | ||
212 | */ | ||
213 | if (high_clock && !(oldclock & 0x20)) | ||
214 | sis630_write(SMB_CNT,(sis630_read(SMB_CNT) & ~0x20)); | ||
215 | |||
216 | dev_dbg(&adap->dev, "SMB_CNT after clock restore 0x%02x\n", sis630_read(SMB_CNT)); | ||
217 | } | ||
218 | |||
219 | static int sis630_transaction(struct i2c_adapter *adap, int size) | ||
220 | { | ||
221 | int result = 0; | ||
222 | u8 oldclock = 0; | ||
223 | |||
224 | result = sis630_transaction_start(adap, size, &oldclock); | ||
225 | if (!result) { | ||
226 | result = sis630_transaction_wait(adap, size); | ||
227 | sis630_transaction_end(adap, oldclock); | ||
228 | } | ||
229 | |||
230 | return result; | ||
231 | } | ||
232 | |||
233 | static int sis630_block_data(struct i2c_adapter *adap, union i2c_smbus_data *data, int read_write) | ||
234 | { | ||
235 | int i, len = 0, rc = 0; | ||
236 | u8 oldclock = 0; | ||
237 | |||
238 | if (read_write == I2C_SMBUS_WRITE) { | ||
239 | len = data->block[0]; | ||
240 | if (len < 0) | ||
241 | len = 0; | ||
242 | else if (len > 32) | ||
243 | len = 32; | ||
244 | sis630_write(SMB_COUNT, len); | ||
245 | for (i=1; i <= len; i++) { | ||
246 | dev_dbg(&adap->dev, "set data 0x%02x\n", data->block[i]); | ||
247 | /* set data */ | ||
248 | sis630_write(SMB_BYTE+(i-1)%8, data->block[i]); | ||
249 | if (i==8 || (len<8 && i==len)) { | ||
250 | dev_dbg(&adap->dev, "start trans len=%d i=%d\n",len ,i); | ||
251 | /* first transaction */ | ||
252 | if (sis630_transaction_start(adap, SIS630_BLOCK_DATA, &oldclock)) | ||
253 | return -1; | ||
254 | } | ||
255 | else if ((i-1)%8 == 7 || i==len) { | ||
256 | dev_dbg(&adap->dev, "trans_wait len=%d i=%d\n",len,i); | ||
257 | if (i>8) { | ||
258 | dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i); | ||
259 | /* | ||
260 | If this is not first transaction, | ||
261 | we must clear sticky bit. | ||
262 | clear SMBARY_STS | ||
263 | */ | ||
264 | sis630_write(SMB_STS,0x10); | ||
265 | } | ||
266 | if (sis630_transaction_wait(adap, SIS630_BLOCK_DATA)) { | ||
267 | dev_dbg(&adap->dev, "trans_wait failed\n"); | ||
268 | rc = -1; | ||
269 | break; | ||
270 | } | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | else { | ||
275 | /* read request */ | ||
276 | data->block[0] = len = 0; | ||
277 | if (sis630_transaction_start(adap, SIS630_BLOCK_DATA, &oldclock)) { | ||
278 | return -1; | ||
279 | } | ||
280 | do { | ||
281 | if (sis630_transaction_wait(adap, SIS630_BLOCK_DATA)) { | ||
282 | dev_dbg(&adap->dev, "trans_wait failed\n"); | ||
283 | rc = -1; | ||
284 | break; | ||
285 | } | ||
286 | /* if this first transaction then read byte count */ | ||
287 | if (len == 0) | ||
288 | data->block[0] = sis630_read(SMB_COUNT); | ||
289 | |||
290 | /* just to be sure */ | ||
291 | if (data->block[0] > 32) | ||
292 | data->block[0] = 32; | ||
293 | |||
294 | dev_dbg(&adap->dev, "block data read len=0x%x\n", data->block[0]); | ||
295 | |||
296 | for (i=0; i < 8 && len < data->block[0]; i++,len++) { | ||
297 | dev_dbg(&adap->dev, "read i=%d len=%d\n", i, len); | ||
298 | data->block[len+1] = sis630_read(SMB_BYTE+i); | ||
299 | } | ||
300 | |||
301 | dev_dbg(&adap->dev, "clear smbary_sts len=%d i=%d\n",len,i); | ||
302 | |||
303 | /* clear SMBARY_STS */ | ||
304 | sis630_write(SMB_STS,0x10); | ||
305 | } while(len < data->block[0]); | ||
306 | } | ||
307 | |||
308 | sis630_transaction_end(adap, oldclock); | ||
309 | |||
310 | return rc; | ||
311 | } | ||
312 | |||
313 | /* Return -1 on error. */ | ||
314 | static s32 sis630_access(struct i2c_adapter *adap, u16 addr, | ||
315 | unsigned short flags, char read_write, | ||
316 | u8 command, int size, union i2c_smbus_data *data) | ||
317 | { | ||
318 | switch (size) { | ||
319 | case I2C_SMBUS_QUICK: | ||
320 | sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); | ||
321 | size = SIS630_QUICK; | ||
322 | break; | ||
323 | case I2C_SMBUS_BYTE: | ||
324 | sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); | ||
325 | if (read_write == I2C_SMBUS_WRITE) | ||
326 | sis630_write(SMB_CMD, command); | ||
327 | size = SIS630_BYTE; | ||
328 | break; | ||
329 | case I2C_SMBUS_BYTE_DATA: | ||
330 | sis630_write(SMB_ADDR, ((addr & 0x7f) << 1) | (read_write & 0x01)); | ||
331 | sis630_write(SMB_CMD, command); | ||
332 | if (read_write == I2C_SMBUS_WRITE) | ||
333 | sis630_write(SMB_BYTE, data->byte); | ||
334 | size = SIS630_BYTE_DATA; | ||
335 | break; | ||
336 | case I2C_SMBUS_PROC_CALL: | ||
337 | case I2C_SMBUS_WORD_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->word & 0xff); | ||
342 | sis630_write(SMB_BYTE + 1,(data->word & 0xff00) >> 8); | ||
343 | } | ||
344 | size = (size == I2C_SMBUS_PROC_CALL ? SIS630_PCALL : SIS630_WORD_DATA); | ||
345 | break; | ||
346 | case I2C_SMBUS_BLOCK_DATA: | ||
347 | sis630_write(SMB_ADDR,((addr & 0x7f) << 1) | (read_write & 0x01)); | ||
348 | sis630_write(SMB_CMD, command); | ||
349 | size = SIS630_BLOCK_DATA; | ||
350 | return sis630_block_data(adap, data, read_write); | ||
351 | default: | ||
352 | printk("Unsupported I2C size\n"); | ||
353 | return -1; | ||
354 | break; | ||
355 | } | ||
356 | |||
357 | if (sis630_transaction(adap, size)) | ||
358 | return -1; | ||
359 | |||
360 | if ((size != SIS630_PCALL) && | ||
361 | ((read_write == I2C_SMBUS_WRITE) || (size == SIS630_QUICK))) { | ||
362 | return 0; | ||
363 | } | ||
364 | |||
365 | switch(size) { | ||
366 | case SIS630_BYTE: | ||
367 | case SIS630_BYTE_DATA: | ||
368 | data->byte = sis630_read(SMB_BYTE); | ||
369 | break; | ||
370 | case SIS630_PCALL: | ||
371 | case SIS630_WORD_DATA: | ||
372 | data->word = sis630_read(SMB_BYTE) + (sis630_read(SMB_BYTE + 1) << 8); | ||
373 | break; | ||
374 | default: | ||
375 | return -1; | ||
376 | break; | ||
377 | } | ||
378 | |||
379 | return 0; | ||
380 | } | ||
381 | |||
382 | static u32 sis630_func(struct i2c_adapter *adapter) | ||
383 | { | ||
384 | return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA | | ||
385 | I2C_FUNC_SMBUS_WORD_DATA | I2C_FUNC_SMBUS_PROC_CALL | | ||
386 | I2C_FUNC_SMBUS_BLOCK_DATA; | ||
387 | } | ||
388 | |||
389 | static int sis630_setup(struct pci_dev *sis630_dev) | ||
390 | { | ||
391 | unsigned char b; | ||
392 | struct pci_dev *dummy = NULL; | ||
393 | int retval = -ENODEV, i; | ||
394 | |||
395 | /* check for supported SiS devices */ | ||
396 | for (i=0; supported[i] > 0 ; i++) { | ||
397 | if ((dummy = pci_get_device(PCI_VENDOR_ID_SI, supported[i], dummy))) | ||
398 | break; /* found */ | ||
399 | } | ||
400 | |||
401 | if (dummy) { | ||
402 | pci_dev_put(dummy); | ||
403 | } | ||
404 | else if (force) { | ||
405 | dev_err(&sis630_dev->dev, "WARNING: Can't detect SIS630 compatible device, but " | ||
406 | "loading because of force option enabled\n"); | ||
407 | } | ||
408 | else { | ||
409 | return -ENODEV; | ||
410 | } | ||
411 | |||
412 | /* | ||
413 | Enable ACPI first , so we can accsess reg 74-75 | ||
414 | in acpi io space and read acpi base addr | ||
415 | */ | ||
416 | if (pci_read_config_byte(sis630_dev, SIS630_BIOS_CTL_REG,&b)) { | ||
417 | dev_err(&sis630_dev->dev, "Error: Can't read bios ctl reg\n"); | ||
418 | goto exit; | ||
419 | } | ||
420 | /* if ACPI already enabled , do nothing */ | ||
421 | if (!(b & 0x80) && | ||
422 | pci_write_config_byte(sis630_dev, SIS630_BIOS_CTL_REG, b | 0x80)) { | ||
423 | dev_err(&sis630_dev->dev, "Error: Can't enable ACPI\n"); | ||
424 | goto exit; | ||
425 | } | ||
426 | |||
427 | /* Determine the ACPI base address */ | ||
428 | if (pci_read_config_word(sis630_dev,SIS630_ACPI_BASE_REG,&acpi_base)) { | ||
429 | dev_err(&sis630_dev->dev, "Error: Can't determine ACPI base address\n"); | ||
430 | goto exit; | ||
431 | } | ||
432 | |||
433 | dev_dbg(&sis630_dev->dev, "ACPI base at 0x%04x\n", acpi_base); | ||
434 | |||
435 | /* Everything is happy, let's grab the memory and set things up. */ | ||
436 | if (!request_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION, "sis630-smbus")) { | ||
437 | dev_err(&sis630_dev->dev, "SMBus registers 0x%04x-0x%04x already " | ||
438 | "in use!\n", acpi_base + SMB_STS, acpi_base + SMB_SAA); | ||
439 | goto exit; | ||
440 | } | ||
441 | |||
442 | retval = 0; | ||
443 | |||
444 | exit: | ||
445 | if (retval) | ||
446 | acpi_base = 0; | ||
447 | return retval; | ||
448 | } | ||
449 | |||
450 | |||
451 | static struct i2c_algorithm smbus_algorithm = { | ||
452 | .name = "Non-I2C SMBus adapter", | ||
453 | .id = I2C_ALGO_SMBUS, | ||
454 | .smbus_xfer = sis630_access, | ||
455 | .functionality = sis630_func, | ||
456 | }; | ||
457 | |||
458 | static struct i2c_adapter sis630_adapter = { | ||
459 | .owner = THIS_MODULE, | ||
460 | .class = I2C_CLASS_HWMON, | ||
461 | .name = "unset", | ||
462 | .algo = &smbus_algorithm, | ||
463 | }; | ||
464 | |||
465 | static struct pci_device_id sis630_ids[] __devinitdata = { | ||
466 | { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_503) }, | ||
467 | { PCI_DEVICE(PCI_VENDOR_ID_SI, PCI_DEVICE_ID_SI_LPC) }, | ||
468 | { 0, } | ||
469 | }; | ||
470 | |||
471 | MODULE_DEVICE_TABLE (pci, sis630_ids); | ||
472 | |||
473 | static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_id *id) | ||
474 | { | ||
475 | if (sis630_setup(dev)) { | ||
476 | dev_err(&dev->dev, "SIS630 comp. bus not detected, module not inserted.\n"); | ||
477 | return -ENODEV; | ||
478 | } | ||
479 | |||
480 | /* set up the driverfs linkage to our parent device */ | ||
481 | sis630_adapter.dev.parent = &dev->dev; | ||
482 | |||
483 | sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x", | ||
484 | acpi_base + SMB_STS); | ||
485 | |||
486 | return i2c_add_adapter(&sis630_adapter); | ||
487 | } | ||
488 | |||
489 | static void __devexit sis630_remove(struct pci_dev *dev) | ||
490 | { | ||
491 | if (acpi_base) { | ||
492 | i2c_del_adapter(&sis630_adapter); | ||
493 | release_region(acpi_base + SMB_STS, SIS630_SMB_IOREGION); | ||
494 | acpi_base = 0; | ||
495 | } | ||
496 | } | ||
497 | |||
498 | |||
499 | static struct pci_driver sis630_driver = { | ||
500 | .name = "sis630_smbus", | ||
501 | .id_table = sis630_ids, | ||
502 | .probe = sis630_probe, | ||
503 | .remove = __devexit_p(sis630_remove), | ||
504 | }; | ||
505 | |||
506 | static int __init i2c_sis630_init(void) | ||
507 | { | ||
508 | return pci_register_driver(&sis630_driver); | ||
509 | } | ||
510 | |||
511 | |||
512 | static void __exit i2c_sis630_exit(void) | ||
513 | { | ||
514 | pci_unregister_driver(&sis630_driver); | ||
515 | } | ||
516 | |||
517 | |||
518 | MODULE_LICENSE("GPL"); | ||
519 | MODULE_AUTHOR("Alexander Malysh <amalysh@web.de>"); | ||
520 | MODULE_DESCRIPTION("SIS630 SMBus driver"); | ||
521 | |||
522 | module_init(i2c_sis630_init); | ||
523 | module_exit(i2c_sis630_exit); | ||