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-viapro.c |
Linux-2.6.12-rc2v2.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-viapro.c')
-rw-r--r-- | drivers/i2c/busses/i2c-viapro.c | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/drivers/i2c/busses/i2c-viapro.c b/drivers/i2c/busses/i2c-viapro.c new file mode 100644 index 000000000000..0bb60a636e16 --- /dev/null +++ b/drivers/i2c/busses/i2c-viapro.c | |||
@@ -0,0 +1,458 @@ | |||
1 | /* | ||
2 | i2c-viapro.c - Part of lm_sensors, Linux kernel modules for hardware | ||
3 | monitoring | ||
4 | Copyright (c) 1998 - 2002 Frodo Looijaard <frodol@dds.nl>, | ||
5 | Philip Edelbrock <phil@netroedge.com>, Kyösti Mälkki <kmalkki@cc.hut.fi>, | ||
6 | Mark D. Studebaker <mdsxyz123@yahoo.com> | ||
7 | |||
8 | This program is free software; you can redistribute it and/or modify | ||
9 | it under the terms of the GNU General Public License as published by | ||
10 | the Free Software Foundation; either version 2 of the License, or | ||
11 | (at your option) any later version. | ||
12 | |||
13 | This program is distributed in the hope that it will be useful, | ||
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | GNU General Public License for more details. | ||
17 | |||
18 | You should have received a copy of the GNU General Public License | ||
19 | along with this program; if not, write to the Free Software | ||
20 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
21 | */ | ||
22 | |||
23 | /* | ||
24 | Supports Via devices: | ||
25 | 82C596A/B (0x3050) | ||
26 | 82C596B (0x3051) | ||
27 | 82C686A/B | ||
28 | 8231 | ||
29 | 8233 | ||
30 | 8233A (0x3147 and 0x3177) | ||
31 | 8235 | ||
32 | 8237 | ||
33 | Note: we assume there can only be one device, with one SMBus interface. | ||
34 | */ | ||
35 | |||
36 | #include <linux/config.h> | ||
37 | #include <linux/module.h> | ||
38 | #include <linux/delay.h> | ||
39 | #include <linux/pci.h> | ||
40 | #include <linux/kernel.h> | ||
41 | #include <linux/stddef.h> | ||
42 | #include <linux/sched.h> | ||
43 | #include <linux/ioport.h> | ||
44 | #include <linux/i2c.h> | ||
45 | #include <linux/init.h> | ||
46 | #include <asm/io.h> | ||
47 | |||
48 | static struct pci_dev *vt596_pdev; | ||
49 | |||
50 | #define SMBBA1 0x90 | ||
51 | #define SMBBA2 0x80 | ||
52 | #define SMBBA3 0xD0 | ||
53 | |||
54 | /* SMBus address offsets */ | ||
55 | static unsigned short vt596_smba; | ||
56 | #define SMBHSTSTS (vt596_smba + 0) | ||
57 | #define SMBHSLVSTS (vt596_smba + 1) | ||
58 | #define SMBHSTCNT (vt596_smba + 2) | ||
59 | #define SMBHSTCMD (vt596_smba + 3) | ||
60 | #define SMBHSTADD (vt596_smba + 4) | ||
61 | #define SMBHSTDAT0 (vt596_smba + 5) | ||
62 | #define SMBHSTDAT1 (vt596_smba + 6) | ||
63 | #define SMBBLKDAT (vt596_smba + 7) | ||
64 | #define SMBSLVCNT (vt596_smba + 8) | ||
65 | #define SMBSHDWCMD (vt596_smba + 9) | ||
66 | #define SMBSLVEVT (vt596_smba + 0xA) | ||
67 | #define SMBSLVDAT (vt596_smba + 0xC) | ||
68 | |||
69 | /* PCI Address Constants */ | ||
70 | |||
71 | /* SMBus data in configuration space can be found in two places, | ||
72 | We try to select the better one*/ | ||
73 | |||
74 | static unsigned short smb_cf_hstcfg = 0xD2; | ||
75 | |||
76 | #define SMBHSTCFG (smb_cf_hstcfg) | ||
77 | #define SMBSLVC (smb_cf_hstcfg + 1) | ||
78 | #define SMBSHDW1 (smb_cf_hstcfg + 2) | ||
79 | #define SMBSHDW2 (smb_cf_hstcfg + 3) | ||
80 | #define SMBREV (smb_cf_hstcfg + 4) | ||
81 | |||
82 | /* Other settings */ | ||
83 | #define MAX_TIMEOUT 500 | ||
84 | #define ENABLE_INT9 0 | ||
85 | |||
86 | /* VT82C596 constants */ | ||
87 | #define VT596_QUICK 0x00 | ||
88 | #define VT596_BYTE 0x04 | ||
89 | #define VT596_BYTE_DATA 0x08 | ||
90 | #define VT596_WORD_DATA 0x0C | ||
91 | #define VT596_BLOCK_DATA 0x14 | ||
92 | |||
93 | |||
94 | /* If force is set to anything different from 0, we forcibly enable the | ||
95 | VT596. DANGEROUS! */ | ||
96 | static int force; | ||
97 | module_param(force, bool, 0); | ||
98 | MODULE_PARM_DESC(force, "Forcibly enable the SMBus. DANGEROUS!"); | ||
99 | |||
100 | /* If force_addr is set to anything different from 0, we forcibly enable | ||
101 | the VT596 at the given address. VERY DANGEROUS! */ | ||
102 | static u16 force_addr; | ||
103 | module_param(force_addr, ushort, 0); | ||
104 | MODULE_PARM_DESC(force_addr, | ||
105 | "Forcibly enable the SMBus at the given address. " | ||
106 | "EXTREMELY DANGEROUS!"); | ||
107 | |||
108 | |||
109 | static struct i2c_adapter vt596_adapter; | ||
110 | |||
111 | /* Another internally used function */ | ||
112 | static int vt596_transaction(void) | ||
113 | { | ||
114 | int temp; | ||
115 | int result = 0; | ||
116 | int timeout = 0; | ||
117 | |||
118 | dev_dbg(&vt596_adapter.dev, "Transaction (pre): CNT=%02x, CMD=%02x, " | ||
119 | "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), | ||
120 | inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), | ||
121 | inb_p(SMBHSTDAT1)); | ||
122 | |||
123 | /* Make sure the SMBus host is ready to start transmitting */ | ||
124 | if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { | ||
125 | dev_dbg(&vt596_adapter.dev, "SMBus busy (0x%02x). " | ||
126 | "Resetting...\n", temp); | ||
127 | |||
128 | outb_p(temp, SMBHSTSTS); | ||
129 | if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { | ||
130 | dev_dbg(&vt596_adapter.dev, "Failed! (0x%02x)\n", temp); | ||
131 | |||
132 | return -1; | ||
133 | } else { | ||
134 | dev_dbg(&vt596_adapter.dev, "Successfull!\n"); | ||
135 | } | ||
136 | } | ||
137 | |||
138 | /* start the transaction by setting bit 6 */ | ||
139 | outb_p(inb(SMBHSTCNT) | 0x040, SMBHSTCNT); | ||
140 | |||
141 | /* We will always wait for a fraction of a second! | ||
142 | I don't know if VIA needs this, Intel did */ | ||
143 | do { | ||
144 | msleep(1); | ||
145 | temp = inb_p(SMBHSTSTS); | ||
146 | } while ((temp & 0x01) && (timeout++ < MAX_TIMEOUT)); | ||
147 | |||
148 | /* If the SMBus is still busy, we give up */ | ||
149 | if (timeout >= MAX_TIMEOUT) { | ||
150 | result = -1; | ||
151 | dev_dbg(&vt596_adapter.dev, "SMBus Timeout!\n"); | ||
152 | } | ||
153 | |||
154 | if (temp & 0x10) { | ||
155 | result = -1; | ||
156 | dev_dbg(&vt596_adapter.dev, "Error: Failed bus transaction\n"); | ||
157 | } | ||
158 | |||
159 | if (temp & 0x08) { | ||
160 | result = -1; | ||
161 | dev_info(&vt596_adapter.dev, "Bus collision! SMBus may be " | ||
162 | "locked until next hard\nreset. (sorry!)\n"); | ||
163 | /* Clock stops and slave is stuck in mid-transmission */ | ||
164 | } | ||
165 | |||
166 | if (temp & 0x04) { | ||
167 | result = -1; | ||
168 | dev_dbg(&vt596_adapter.dev, "Error: no response!\n"); | ||
169 | } | ||
170 | |||
171 | if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { | ||
172 | outb_p(temp, SMBHSTSTS); | ||
173 | if ((temp = inb_p(SMBHSTSTS)) & 0x1F) { | ||
174 | dev_warn(&vt596_adapter.dev, "Failed reset at end " | ||
175 | "of transaction (%02x)\n", temp); | ||
176 | } | ||
177 | } | ||
178 | |||
179 | dev_dbg(&vt596_adapter.dev, "Transaction (post): CNT=%02x, CMD=%02x, " | ||
180 | "ADD=%02x, DAT0=%02x, DAT1=%02x\n", inb_p(SMBHSTCNT), | ||
181 | inb_p(SMBHSTCMD), inb_p(SMBHSTADD), inb_p(SMBHSTDAT0), | ||
182 | inb_p(SMBHSTDAT1)); | ||
183 | |||
184 | return result; | ||
185 | } | ||
186 | |||
187 | /* Return -1 on error. */ | ||
188 | static s32 vt596_access(struct i2c_adapter *adap, u16 addr, | ||
189 | unsigned short flags, char read_write, u8 command, | ||
190 | int size, union i2c_smbus_data *data) | ||
191 | { | ||
192 | int i, len; | ||
193 | |||
194 | switch (size) { | ||
195 | case I2C_SMBUS_PROC_CALL: | ||
196 | dev_info(&vt596_adapter.dev, | ||
197 | "I2C_SMBUS_PROC_CALL not supported!\n"); | ||
198 | return -1; | ||
199 | case I2C_SMBUS_QUICK: | ||
200 | outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), | ||
201 | SMBHSTADD); | ||
202 | size = VT596_QUICK; | ||
203 | break; | ||
204 | case I2C_SMBUS_BYTE: | ||
205 | outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), | ||
206 | SMBHSTADD); | ||
207 | if (read_write == I2C_SMBUS_WRITE) | ||
208 | outb_p(command, SMBHSTCMD); | ||
209 | size = VT596_BYTE; | ||
210 | break; | ||
211 | case I2C_SMBUS_BYTE_DATA: | ||
212 | outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), | ||
213 | SMBHSTADD); | ||
214 | outb_p(command, SMBHSTCMD); | ||
215 | if (read_write == I2C_SMBUS_WRITE) | ||
216 | outb_p(data->byte, SMBHSTDAT0); | ||
217 | size = VT596_BYTE_DATA; | ||
218 | break; | ||
219 | case I2C_SMBUS_WORD_DATA: | ||
220 | outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), | ||
221 | SMBHSTADD); | ||
222 | outb_p(command, SMBHSTCMD); | ||
223 | if (read_write == I2C_SMBUS_WRITE) { | ||
224 | outb_p(data->word & 0xff, SMBHSTDAT0); | ||
225 | outb_p((data->word & 0xff00) >> 8, SMBHSTDAT1); | ||
226 | } | ||
227 | size = VT596_WORD_DATA; | ||
228 | break; | ||
229 | case I2C_SMBUS_BLOCK_DATA: | ||
230 | outb_p(((addr & 0x7f) << 1) | (read_write & 0x01), | ||
231 | SMBHSTADD); | ||
232 | outb_p(command, SMBHSTCMD); | ||
233 | if (read_write == I2C_SMBUS_WRITE) { | ||
234 | len = data->block[0]; | ||
235 | if (len < 0) | ||
236 | len = 0; | ||
237 | if (len > I2C_SMBUS_BLOCK_MAX) | ||
238 | len = I2C_SMBUS_BLOCK_MAX; | ||
239 | outb_p(len, SMBHSTDAT0); | ||
240 | i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ | ||
241 | for (i = 1; i <= len; i++) | ||
242 | outb_p(data->block[i], SMBBLKDAT); | ||
243 | } | ||
244 | size = VT596_BLOCK_DATA; | ||
245 | break; | ||
246 | } | ||
247 | |||
248 | outb_p((size & 0x1C) + (ENABLE_INT9 & 1), SMBHSTCNT); | ||
249 | |||
250 | if (vt596_transaction()) /* Error in transaction */ | ||
251 | return -1; | ||
252 | |||
253 | if ((read_write == I2C_SMBUS_WRITE) || (size == VT596_QUICK)) | ||
254 | return 0; | ||
255 | |||
256 | switch (size) { | ||
257 | case VT596_BYTE: | ||
258 | /* Where is the result put? I assume here it is in | ||
259 | * SMBHSTDAT0 but it might just as well be in the | ||
260 | * SMBHSTCMD. No clue in the docs | ||
261 | */ | ||
262 | data->byte = inb_p(SMBHSTDAT0); | ||
263 | break; | ||
264 | case VT596_BYTE_DATA: | ||
265 | data->byte = inb_p(SMBHSTDAT0); | ||
266 | break; | ||
267 | case VT596_WORD_DATA: | ||
268 | data->word = inb_p(SMBHSTDAT0) + (inb_p(SMBHSTDAT1) << 8); | ||
269 | break; | ||
270 | case VT596_BLOCK_DATA: | ||
271 | data->block[0] = inb_p(SMBHSTDAT0); | ||
272 | if (data->block[0] > I2C_SMBUS_BLOCK_MAX) | ||
273 | data->block[0] = I2C_SMBUS_BLOCK_MAX; | ||
274 | i = inb_p(SMBHSTCNT); /* Reset SMBBLKDAT */ | ||
275 | for (i = 1; i <= data->block[0]; i++) | ||
276 | data->block[i] = inb_p(SMBBLKDAT); | ||
277 | break; | ||
278 | } | ||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | static u32 vt596_func(struct i2c_adapter *adapter) | ||
283 | { | ||
284 | return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | | ||
285 | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | | ||
286 | I2C_FUNC_SMBUS_BLOCK_DATA; | ||
287 | } | ||
288 | |||
289 | static struct i2c_algorithm smbus_algorithm = { | ||
290 | .name = "Non-I2C SMBus adapter", | ||
291 | .id = I2C_ALGO_SMBUS, | ||
292 | .smbus_xfer = vt596_access, | ||
293 | .functionality = vt596_func, | ||
294 | }; | ||
295 | |||
296 | static struct i2c_adapter vt596_adapter = { | ||
297 | .owner = THIS_MODULE, | ||
298 | .class = I2C_CLASS_HWMON, | ||
299 | .algo = &smbus_algorithm, | ||
300 | .name = "unset", | ||
301 | }; | ||
302 | |||
303 | static int __devinit vt596_probe(struct pci_dev *pdev, | ||
304 | const struct pci_device_id *id) | ||
305 | { | ||
306 | unsigned char temp; | ||
307 | int error = -ENODEV; | ||
308 | |||
309 | /* Determine the address of the SMBus areas */ | ||
310 | if (force_addr) { | ||
311 | vt596_smba = force_addr & 0xfff0; | ||
312 | force = 0; | ||
313 | goto found; | ||
314 | } | ||
315 | |||
316 | if ((pci_read_config_word(pdev, id->driver_data, &vt596_smba)) || | ||
317 | !(vt596_smba & 0x1)) { | ||
318 | /* try 2nd address and config reg. for 596 */ | ||
319 | if (id->device == PCI_DEVICE_ID_VIA_82C596_3 && | ||
320 | !pci_read_config_word(pdev, SMBBA2, &vt596_smba) && | ||
321 | (vt596_smba & 0x1)) { | ||
322 | smb_cf_hstcfg = 0x84; | ||
323 | } else { | ||
324 | /* no matches at all */ | ||
325 | dev_err(&pdev->dev, "Cannot configure " | ||
326 | "SMBus I/O Base address\n"); | ||
327 | return -ENODEV; | ||
328 | } | ||
329 | } | ||
330 | |||
331 | vt596_smba &= 0xfff0; | ||
332 | if (vt596_smba == 0) { | ||
333 | dev_err(&pdev->dev, "SMBus base address " | ||
334 | "uninitialized - upgrade BIOS or use " | ||
335 | "force_addr=0xaddr\n"); | ||
336 | return -ENODEV; | ||
337 | } | ||
338 | |||
339 | found: | ||
340 | if (!request_region(vt596_smba, 8, "viapro-smbus")) { | ||
341 | dev_err(&pdev->dev, "SMBus region 0x%x already in use!\n", | ||
342 | vt596_smba); | ||
343 | return -ENODEV; | ||
344 | } | ||
345 | |||
346 | pci_read_config_byte(pdev, SMBHSTCFG, &temp); | ||
347 | /* If force_addr is set, we program the new address here. Just to make | ||
348 | sure, we disable the VT596 first. */ | ||
349 | if (force_addr) { | ||
350 | pci_write_config_byte(pdev, SMBHSTCFG, temp & 0xfe); | ||
351 | pci_write_config_word(pdev, id->driver_data, vt596_smba); | ||
352 | pci_write_config_byte(pdev, SMBHSTCFG, temp | 0x01); | ||
353 | dev_warn(&pdev->dev, "WARNING: SMBus interface set to new " | ||
354 | "address 0x%04x!\n", vt596_smba); | ||
355 | } else if ((temp & 1) == 0) { | ||
356 | if (force) { | ||
357 | /* NOTE: This assumes I/O space and other allocations | ||
358 | * WERE done by the Bios! Don't complain if your | ||
359 | * hardware does weird things after enabling this. | ||
360 | * :') Check for Bios updates before resorting to | ||
361 | * this. | ||
362 | */ | ||
363 | pci_write_config_byte(pdev, SMBHSTCFG, temp | 1); | ||
364 | dev_info(&pdev->dev, "Enabling SMBus device\n"); | ||
365 | } else { | ||
366 | dev_err(&pdev->dev, "SMBUS: Error: Host SMBus " | ||
367 | "controller not enabled! - upgrade BIOS or " | ||
368 | "use force=1\n"); | ||
369 | goto release_region; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | if ((temp & 0x0E) == 8) | ||
374 | dev_dbg(&pdev->dev, "using Interrupt 9 for SMBus.\n"); | ||
375 | else if ((temp & 0x0E) == 0) | ||
376 | dev_dbg(&pdev->dev, "using Interrupt SMI# for SMBus.\n"); | ||
377 | else | ||
378 | dev_dbg(&pdev->dev, "Illegal Interrupt configuration " | ||
379 | "(or code out of date)!\n"); | ||
380 | |||
381 | pci_read_config_byte(pdev, SMBREV, &temp); | ||
382 | dev_dbg(&pdev->dev, "SMBREV = 0x%X\n", temp); | ||
383 | dev_dbg(&pdev->dev, "VT596_smba = 0x%X\n", vt596_smba); | ||
384 | |||
385 | vt596_adapter.dev.parent = &pdev->dev; | ||
386 | snprintf(vt596_adapter.name, I2C_NAME_SIZE, | ||
387 | "SMBus Via Pro adapter at %04x", vt596_smba); | ||
388 | |||
389 | vt596_pdev = pci_dev_get(pdev); | ||
390 | if (i2c_add_adapter(&vt596_adapter)) { | ||
391 | pci_dev_put(vt596_pdev); | ||
392 | vt596_pdev = NULL; | ||
393 | } | ||
394 | |||
395 | /* Always return failure here. This is to allow other drivers to bind | ||
396 | * to this pci device. We don't really want to have control over the | ||
397 | * pci device, we only wanted to read as few register values from it. | ||
398 | */ | ||
399 | return -ENODEV; | ||
400 | |||
401 | release_region: | ||
402 | release_region(vt596_smba, 8); | ||
403 | return error; | ||
404 | } | ||
405 | |||
406 | static struct pci_device_id vt596_ids[] = { | ||
407 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C596_3), | ||
408 | .driver_data = SMBBA1 }, | ||
409 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C596B_3), | ||
410 | .driver_data = SMBBA1 }, | ||
411 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_82C686_4), | ||
412 | .driver_data = SMBBA1 }, | ||
413 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233_0), | ||
414 | .driver_data = SMBBA3 }, | ||
415 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8233A), | ||
416 | .driver_data = SMBBA3 }, | ||
417 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8235), | ||
418 | .driver_data = SMBBA3 }, | ||
419 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8237), | ||
420 | .driver_data = SMBBA3 }, | ||
421 | { PCI_DEVICE(PCI_VENDOR_ID_VIA, PCI_DEVICE_ID_VIA_8231_4), | ||
422 | .driver_data = SMBBA1 }, | ||
423 | { 0, } | ||
424 | }; | ||
425 | |||
426 | MODULE_DEVICE_TABLE (pci, vt596_ids); | ||
427 | |||
428 | static struct pci_driver vt596_driver = { | ||
429 | .name = "vt596_smbus", | ||
430 | .id_table = vt596_ids, | ||
431 | .probe = vt596_probe, | ||
432 | }; | ||
433 | |||
434 | static int __init i2c_vt596_init(void) | ||
435 | { | ||
436 | return pci_register_driver(&vt596_driver); | ||
437 | } | ||
438 | |||
439 | |||
440 | static void __exit i2c_vt596_exit(void) | ||
441 | { | ||
442 | pci_unregister_driver(&vt596_driver); | ||
443 | if (vt596_pdev != NULL) { | ||
444 | i2c_del_adapter(&vt596_adapter); | ||
445 | release_region(vt596_smba, 8); | ||
446 | pci_dev_put(vt596_pdev); | ||
447 | vt596_pdev = NULL; | ||
448 | } | ||
449 | } | ||
450 | |||
451 | MODULE_AUTHOR( | ||
452 | "Frodo Looijaard <frodol@dds.nl> and " | ||
453 | "Philip Edelbrock <phil@netroedge.com>"); | ||
454 | MODULE_DESCRIPTION("vt82c596 SMBus driver"); | ||
455 | MODULE_LICENSE("GPL"); | ||
456 | |||
457 | module_init(i2c_vt596_init); | ||
458 | module_exit(i2c_vt596_exit); | ||