| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 1 | /* | 
|  | 2 | * Copyright (C) 2006-2007 PA Semi, Inc | 
|  | 3 | * | 
|  | 4 | * SMBus host driver for PA Semi PWRficient | 
|  | 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 version 2 as | 
|  | 8 | * published by the Free Software Foundation. | 
|  | 9 | * | 
|  | 10 | * This program is distributed in the hope that it will be useful, | 
|  | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
|  | 13 | * GNU General Public License for more details. | 
|  | 14 | * | 
|  | 15 | * You should have received a copy of the GNU General Public License | 
|  | 16 | * along with this program; if not, write to the Free Software | 
|  | 17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA | 
|  | 18 | */ | 
|  | 19 |  | 
|  | 20 | #include <linux/module.h> | 
|  | 21 | #include <linux/pci.h> | 
|  | 22 | #include <linux/kernel.h> | 
|  | 23 | #include <linux/stddef.h> | 
|  | 24 | #include <linux/sched.h> | 
|  | 25 | #include <linux/i2c.h> | 
|  | 26 | #include <linux/delay.h> | 
|  | 27 | #include <asm/io.h> | 
|  | 28 |  | 
|  | 29 | static struct pci_driver pasemi_smb_driver; | 
|  | 30 |  | 
|  | 31 | struct pasemi_smbus { | 
|  | 32 | struct pci_dev		*dev; | 
|  | 33 | struct i2c_adapter	 adapter; | 
|  | 34 | unsigned long		 base; | 
|  | 35 | int			 size; | 
|  | 36 | }; | 
|  | 37 |  | 
|  | 38 | /* Register offsets */ | 
|  | 39 | #define REG_MTXFIFO	0x00 | 
|  | 40 | #define REG_MRXFIFO	0x04 | 
|  | 41 | #define REG_SMSTA	0x14 | 
|  | 42 | #define REG_CTL		0x1c | 
|  | 43 |  | 
|  | 44 | /* Register defs */ | 
|  | 45 | #define MTXFIFO_READ	0x00000400 | 
|  | 46 | #define MTXFIFO_STOP	0x00000200 | 
|  | 47 | #define MTXFIFO_START	0x00000100 | 
|  | 48 | #define MTXFIFO_DATA_M	0x000000ff | 
|  | 49 |  | 
|  | 50 | #define MRXFIFO_EMPTY	0x00000100 | 
|  | 51 | #define MRXFIFO_DATA_M	0x000000ff | 
|  | 52 |  | 
|  | 53 | #define SMSTA_XEN	0x08000000 | 
| Olof Johansson | be8a1f7 | 2007-11-15 19:24:02 +0100 | [diff] [blame] | 54 | #define SMSTA_MTN	0x00200000 | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 55 |  | 
|  | 56 | #define CTL_MRR		0x00000400 | 
|  | 57 | #define CTL_MTR		0x00000200 | 
|  | 58 | #define CTL_CLK_M	0x000000ff | 
|  | 59 |  | 
|  | 60 | #define CLK_100K_DIV	84 | 
|  | 61 | #define CLK_400K_DIV	21 | 
|  | 62 |  | 
|  | 63 | static inline void reg_write(struct pasemi_smbus *smbus, int reg, int val) | 
|  | 64 | { | 
|  | 65 | dev_dbg(&smbus->dev->dev, "smbus write reg %lx val %08x\n", | 
|  | 66 | smbus->base + reg, val); | 
|  | 67 | outl(val, smbus->base + reg); | 
|  | 68 | } | 
|  | 69 |  | 
|  | 70 | static inline int reg_read(struct pasemi_smbus *smbus, int reg) | 
|  | 71 | { | 
|  | 72 | int ret; | 
|  | 73 | ret = inl(smbus->base + reg); | 
|  | 74 | dev_dbg(&smbus->dev->dev, "smbus read reg %lx val %08x\n", | 
|  | 75 | smbus->base + reg, ret); | 
|  | 76 | return ret; | 
|  | 77 | } | 
|  | 78 |  | 
|  | 79 | #define TXFIFO_WR(smbus, reg)	reg_write((smbus), REG_MTXFIFO, (reg)) | 
|  | 80 | #define RXFIFO_RD(smbus)	reg_read((smbus), REG_MRXFIFO) | 
|  | 81 |  | 
|  | 82 | static void pasemi_smb_clear(struct pasemi_smbus *smbus) | 
|  | 83 | { | 
|  | 84 | unsigned int status; | 
|  | 85 |  | 
|  | 86 | status = reg_read(smbus, REG_SMSTA); | 
|  | 87 | reg_write(smbus, REG_SMSTA, status); | 
|  | 88 | } | 
|  | 89 |  | 
|  | 90 | static unsigned int pasemi_smb_waitready(struct pasemi_smbus *smbus) | 
|  | 91 | { | 
|  | 92 | int timeout = 10; | 
|  | 93 | unsigned int status; | 
|  | 94 |  | 
|  | 95 | status = reg_read(smbus, REG_SMSTA); | 
|  | 96 |  | 
|  | 97 | while (!(status & SMSTA_XEN) && timeout--) { | 
|  | 98 | msleep(1); | 
|  | 99 | status = reg_read(smbus, REG_SMSTA); | 
|  | 100 | } | 
|  | 101 |  | 
| Olof Johansson | be8a1f7 | 2007-11-15 19:24:02 +0100 | [diff] [blame] | 102 | /* Got NACK? */ | 
|  | 103 | if (status & SMSTA_MTN) | 
|  | 104 | return -ENXIO; | 
|  | 105 |  | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 106 | if (timeout < 0) { | 
|  | 107 | dev_warn(&smbus->dev->dev, "Timeout, status 0x%08x\n", status); | 
|  | 108 | reg_write(smbus, REG_SMSTA, status); | 
|  | 109 | return -ETIME; | 
|  | 110 | } | 
|  | 111 |  | 
|  | 112 | /* Clear XEN */ | 
|  | 113 | reg_write(smbus, REG_SMSTA, SMSTA_XEN); | 
|  | 114 |  | 
|  | 115 | return 0; | 
|  | 116 | } | 
|  | 117 |  | 
|  | 118 | static int pasemi_i2c_xfer_msg(struct i2c_adapter *adapter, | 
|  | 119 | struct i2c_msg *msg, int stop) | 
|  | 120 | { | 
|  | 121 | struct pasemi_smbus *smbus = adapter->algo_data; | 
|  | 122 | int read, i, err; | 
|  | 123 | u32 rd; | 
|  | 124 |  | 
|  | 125 | read = msg->flags & I2C_M_RD ? 1 : 0; | 
|  | 126 |  | 
|  | 127 | TXFIFO_WR(smbus, MTXFIFO_START | (msg->addr << 1) | read); | 
|  | 128 |  | 
|  | 129 | if (read) { | 
|  | 130 | TXFIFO_WR(smbus, msg->len | MTXFIFO_READ | | 
|  | 131 | (stop ? MTXFIFO_STOP : 0)); | 
|  | 132 |  | 
|  | 133 | err = pasemi_smb_waitready(smbus); | 
|  | 134 | if (err) | 
|  | 135 | goto reset_out; | 
|  | 136 |  | 
|  | 137 | for (i = 0; i < msg->len; i++) { | 
|  | 138 | rd = RXFIFO_RD(smbus); | 
|  | 139 | if (rd & MRXFIFO_EMPTY) { | 
|  | 140 | err = -ENODATA; | 
|  | 141 | goto reset_out; | 
|  | 142 | } | 
|  | 143 | msg->buf[i] = rd & MRXFIFO_DATA_M; | 
|  | 144 | } | 
|  | 145 | } else { | 
|  | 146 | for (i = 0; i < msg->len - 1; i++) | 
|  | 147 | TXFIFO_WR(smbus, msg->buf[i]); | 
|  | 148 |  | 
| Olof Johansson | 080dfbe | 2007-04-17 00:32:29 -0700 | [diff] [blame] | 149 | TXFIFO_WR(smbus, msg->buf[msg->len-1] | | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 150 | (stop ? MTXFIFO_STOP : 0)); | 
|  | 151 | } | 
|  | 152 |  | 
|  | 153 | return 0; | 
|  | 154 |  | 
|  | 155 | reset_out: | 
|  | 156 | reg_write(smbus, REG_CTL, (CTL_MTR | CTL_MRR | | 
|  | 157 | (CLK_100K_DIV & CTL_CLK_M))); | 
|  | 158 | return err; | 
|  | 159 | } | 
|  | 160 |  | 
|  | 161 | static int pasemi_i2c_xfer(struct i2c_adapter *adapter, | 
|  | 162 | struct i2c_msg *msgs, int num) | 
|  | 163 | { | 
|  | 164 | struct pasemi_smbus *smbus = adapter->algo_data; | 
|  | 165 | int ret, i; | 
|  | 166 |  | 
|  | 167 | pasemi_smb_clear(smbus); | 
|  | 168 |  | 
|  | 169 | ret = 0; | 
|  | 170 |  | 
|  | 171 | for (i = 0; i < num && !ret; i++) | 
|  | 172 | ret = pasemi_i2c_xfer_msg(adapter, &msgs[i], (i == (num - 1))); | 
|  | 173 |  | 
|  | 174 | return ret ? ret : num; | 
|  | 175 | } | 
|  | 176 |  | 
|  | 177 | static int pasemi_smb_xfer(struct i2c_adapter *adapter, | 
|  | 178 | u16 addr, unsigned short flags, char read_write, u8 command, | 
|  | 179 | int size, union i2c_smbus_data *data) | 
|  | 180 | { | 
|  | 181 | struct pasemi_smbus *smbus = adapter->algo_data; | 
|  | 182 | unsigned int rd; | 
|  | 183 | int read_flag, err; | 
|  | 184 | int len = 0, i; | 
|  | 185 |  | 
|  | 186 | /* All our ops take 8-bit shifted addresses */ | 
|  | 187 | addr <<= 1; | 
|  | 188 | read_flag = read_write == I2C_SMBUS_READ; | 
|  | 189 |  | 
|  | 190 | pasemi_smb_clear(smbus); | 
|  | 191 |  | 
|  | 192 | switch (size) { | 
|  | 193 | case I2C_SMBUS_QUICK: | 
|  | 194 | TXFIFO_WR(smbus, addr | read_flag | MTXFIFO_START | | 
|  | 195 | MTXFIFO_STOP); | 
|  | 196 | break; | 
|  | 197 | case I2C_SMBUS_BYTE: | 
|  | 198 | TXFIFO_WR(smbus, addr | read_flag | MTXFIFO_START); | 
|  | 199 | if (read_write) | 
|  | 200 | TXFIFO_WR(smbus, 1 | MTXFIFO_STOP | MTXFIFO_READ); | 
|  | 201 | else | 
|  | 202 | TXFIFO_WR(smbus, MTXFIFO_STOP | command); | 
|  | 203 | break; | 
|  | 204 | case I2C_SMBUS_BYTE_DATA: | 
|  | 205 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | 
|  | 206 | TXFIFO_WR(smbus, command); | 
|  | 207 | if (read_write) { | 
|  | 208 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | 
|  | 209 | TXFIFO_WR(smbus, 1 | MTXFIFO_READ | MTXFIFO_STOP); | 
|  | 210 | } else { | 
|  | 211 | TXFIFO_WR(smbus, MTXFIFO_STOP | data->byte); | 
|  | 212 | } | 
|  | 213 | break; | 
|  | 214 | case I2C_SMBUS_WORD_DATA: | 
|  | 215 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | 
|  | 216 | TXFIFO_WR(smbus, command); | 
|  | 217 | if (read_write) { | 
|  | 218 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | 
|  | 219 | TXFIFO_WR(smbus, 2 | MTXFIFO_READ | MTXFIFO_STOP); | 
|  | 220 | } else { | 
|  | 221 | TXFIFO_WR(smbus, data->word & MTXFIFO_DATA_M); | 
|  | 222 | TXFIFO_WR(smbus, MTXFIFO_STOP | (data->word >> 8)); | 
|  | 223 | } | 
|  | 224 | break; | 
|  | 225 | case I2C_SMBUS_BLOCK_DATA: | 
|  | 226 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | 
|  | 227 | TXFIFO_WR(smbus, command); | 
|  | 228 | if (read_write) { | 
|  | 229 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | 
|  | 230 | TXFIFO_WR(smbus, 1 | MTXFIFO_READ); | 
|  | 231 | rd = RXFIFO_RD(smbus); | 
|  | 232 | len = min_t(u8, (rd & MRXFIFO_DATA_M), | 
|  | 233 | I2C_SMBUS_BLOCK_MAX); | 
| Olof Johansson | 080dfbe | 2007-04-17 00:32:29 -0700 | [diff] [blame] | 234 | TXFIFO_WR(smbus, len | MTXFIFO_READ | | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 235 | MTXFIFO_STOP); | 
|  | 236 | } else { | 
|  | 237 | len = min_t(u8, data->block[0], I2C_SMBUS_BLOCK_MAX); | 
|  | 238 | TXFIFO_WR(smbus, len); | 
|  | 239 | for (i = 1; i < len; i++) | 
|  | 240 | TXFIFO_WR(smbus, data->block[i]); | 
|  | 241 | TXFIFO_WR(smbus, data->block[len] | MTXFIFO_STOP); | 
|  | 242 | } | 
|  | 243 | break; | 
|  | 244 | case I2C_SMBUS_PROC_CALL: | 
|  | 245 | read_write = I2C_SMBUS_READ; | 
|  | 246 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | 
|  | 247 | TXFIFO_WR(smbus, command); | 
|  | 248 | TXFIFO_WR(smbus, data->word & MTXFIFO_DATA_M); | 
|  | 249 | TXFIFO_WR(smbus, (data->word >> 8) & MTXFIFO_DATA_M); | 
|  | 250 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ | MTXFIFO_START); | 
|  | 251 | TXFIFO_WR(smbus, 2 | MTXFIFO_STOP | MTXFIFO_READ); | 
|  | 252 | break; | 
|  | 253 | case I2C_SMBUS_BLOCK_PROC_CALL: | 
|  | 254 | len = min_t(u8, data->block[0], I2C_SMBUS_BLOCK_MAX - 1); | 
|  | 255 | read_write = I2C_SMBUS_READ; | 
|  | 256 | TXFIFO_WR(smbus, addr | MTXFIFO_START); | 
|  | 257 | TXFIFO_WR(smbus, command); | 
|  | 258 | TXFIFO_WR(smbus, len); | 
|  | 259 | for (i = 1; i <= len; i++) | 
|  | 260 | TXFIFO_WR(smbus, data->block[i]); | 
|  | 261 | TXFIFO_WR(smbus, addr | I2C_SMBUS_READ); | 
|  | 262 | TXFIFO_WR(smbus, MTXFIFO_READ | 1); | 
|  | 263 | rd = RXFIFO_RD(smbus); | 
|  | 264 | len = min_t(u8, (rd & MRXFIFO_DATA_M), | 
|  | 265 | I2C_SMBUS_BLOCK_MAX - len); | 
| Olof Johansson | 080dfbe | 2007-04-17 00:32:29 -0700 | [diff] [blame] | 266 | TXFIFO_WR(smbus, len | MTXFIFO_READ | MTXFIFO_STOP); | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 267 | break; | 
|  | 268 |  | 
|  | 269 | default: | 
|  | 270 | dev_warn(&adapter->dev, "Unsupported transaction %d\n", size); | 
|  | 271 | return -EINVAL; | 
|  | 272 | } | 
|  | 273 |  | 
|  | 274 | err = pasemi_smb_waitready(smbus); | 
|  | 275 | if (err) | 
|  | 276 | goto reset_out; | 
|  | 277 |  | 
|  | 278 | if (read_write == I2C_SMBUS_WRITE) | 
|  | 279 | return 0; | 
|  | 280 |  | 
|  | 281 | switch (size) { | 
|  | 282 | case I2C_SMBUS_BYTE: | 
|  | 283 | case I2C_SMBUS_BYTE_DATA: | 
|  | 284 | rd = RXFIFO_RD(smbus); | 
|  | 285 | if (rd & MRXFIFO_EMPTY) { | 
|  | 286 | err = -ENODATA; | 
|  | 287 | goto reset_out; | 
|  | 288 | } | 
|  | 289 | data->byte = rd & MRXFIFO_DATA_M; | 
|  | 290 | break; | 
|  | 291 | case I2C_SMBUS_WORD_DATA: | 
|  | 292 | case I2C_SMBUS_PROC_CALL: | 
|  | 293 | rd = RXFIFO_RD(smbus); | 
|  | 294 | if (rd & MRXFIFO_EMPTY) { | 
|  | 295 | err = -ENODATA; | 
|  | 296 | goto reset_out; | 
|  | 297 | } | 
|  | 298 | data->word = rd & MRXFIFO_DATA_M; | 
|  | 299 | rd = RXFIFO_RD(smbus); | 
|  | 300 | if (rd & MRXFIFO_EMPTY) { | 
|  | 301 | err = -ENODATA; | 
|  | 302 | goto reset_out; | 
|  | 303 | } | 
|  | 304 | data->word |= (rd & MRXFIFO_DATA_M) << 8; | 
|  | 305 | break; | 
|  | 306 | case I2C_SMBUS_BLOCK_DATA: | 
|  | 307 | case I2C_SMBUS_BLOCK_PROC_CALL: | 
|  | 308 | data->block[0] = len; | 
|  | 309 | for (i = 1; i <= len; i ++) { | 
|  | 310 | rd = RXFIFO_RD(smbus); | 
|  | 311 | if (rd & MRXFIFO_EMPTY) { | 
|  | 312 | err = -ENODATA; | 
|  | 313 | goto reset_out; | 
|  | 314 | } | 
|  | 315 | data->block[i] = rd & MRXFIFO_DATA_M; | 
|  | 316 | } | 
|  | 317 | break; | 
|  | 318 | } | 
|  | 319 |  | 
|  | 320 | return 0; | 
|  | 321 |  | 
|  | 322 | reset_out: | 
|  | 323 | reg_write(smbus, REG_CTL, (CTL_MTR | CTL_MRR | | 
|  | 324 | (CLK_100K_DIV & CTL_CLK_M))); | 
|  | 325 | return err; | 
|  | 326 | } | 
|  | 327 |  | 
|  | 328 | static u32 pasemi_smb_func(struct i2c_adapter *adapter) | 
|  | 329 | { | 
|  | 330 | return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | | 
|  | 331 | I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | | 
|  | 332 | I2C_FUNC_SMBUS_BLOCK_DATA | I2C_FUNC_SMBUS_PROC_CALL | | 
|  | 333 | I2C_FUNC_SMBUS_BLOCK_PROC_CALL | I2C_FUNC_I2C; | 
|  | 334 | } | 
|  | 335 |  | 
|  | 336 | static const struct i2c_algorithm smbus_algorithm = { | 
|  | 337 | .master_xfer	= pasemi_i2c_xfer, | 
|  | 338 | .smbus_xfer	= pasemi_smb_xfer, | 
|  | 339 | .functionality	= pasemi_smb_func, | 
|  | 340 | }; | 
|  | 341 |  | 
|  | 342 | static int __devinit pasemi_smb_probe(struct pci_dev *dev, | 
|  | 343 | const struct pci_device_id *id) | 
|  | 344 | { | 
|  | 345 | struct pasemi_smbus *smbus; | 
|  | 346 | int error; | 
|  | 347 |  | 
|  | 348 | if (!(pci_resource_flags(dev, 0) & IORESOURCE_IO)) | 
|  | 349 | return -ENODEV; | 
|  | 350 |  | 
|  | 351 | smbus = kzalloc(sizeof(struct pasemi_smbus), GFP_KERNEL); | 
|  | 352 | if (!smbus) | 
|  | 353 | return -ENOMEM; | 
|  | 354 |  | 
|  | 355 | smbus->dev = dev; | 
|  | 356 | smbus->base = pci_resource_start(dev, 0); | 
|  | 357 | smbus->size = pci_resource_len(dev, 0); | 
|  | 358 |  | 
|  | 359 | if (!request_region(smbus->base, smbus->size, | 
|  | 360 | pasemi_smb_driver.name)) { | 
|  | 361 | error = -EBUSY; | 
|  | 362 | goto out_kfree; | 
|  | 363 | } | 
|  | 364 |  | 
|  | 365 | smbus->adapter.owner = THIS_MODULE; | 
| David Brownell | 2096b95 | 2007-05-01 23:26:28 +0200 | [diff] [blame] | 366 | snprintf(smbus->adapter.name, sizeof(smbus->adapter.name), | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 367 | "PA Semi SMBus adapter at 0x%lx", smbus->base); | 
|  | 368 | smbus->adapter.class = I2C_CLASS_HWMON; | 
|  | 369 | smbus->adapter.algo = &smbus_algorithm; | 
|  | 370 | smbus->adapter.algo_data = smbus; | 
| Olof Johansson | ccf60d8 | 2008-01-27 18:14:44 +0100 | [diff] [blame] | 371 | smbus->adapter.nr = PCI_FUNC(dev->devfn); | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 372 |  | 
| Robert P. J. Day | ff23f3e | 2007-11-15 19:24:02 +0100 | [diff] [blame] | 373 | /* set up the sysfs linkage to our parent device */ | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 374 | smbus->adapter.dev.parent = &dev->dev; | 
|  | 375 |  | 
|  | 376 | reg_write(smbus, REG_CTL, (CTL_MTR | CTL_MRR | | 
|  | 377 | (CLK_100K_DIV & CTL_CLK_M))); | 
|  | 378 |  | 
| Olof Johansson | ccf60d8 | 2008-01-27 18:14:44 +0100 | [diff] [blame] | 379 | error = i2c_add_numbered_adapter(&smbus->adapter); | 
| Olof Johansson | beb58aa | 2007-02-13 22:09:03 +0100 | [diff] [blame] | 380 | if (error) | 
|  | 381 | goto out_release_region; | 
|  | 382 |  | 
|  | 383 | pci_set_drvdata(dev, smbus); | 
|  | 384 |  | 
|  | 385 | return 0; | 
|  | 386 |  | 
|  | 387 | out_release_region: | 
|  | 388 | release_region(smbus->base, smbus->size); | 
|  | 389 | out_kfree: | 
|  | 390 | kfree(smbus); | 
|  | 391 | return error; | 
|  | 392 | } | 
|  | 393 |  | 
|  | 394 | static void __devexit pasemi_smb_remove(struct pci_dev *dev) | 
|  | 395 | { | 
|  | 396 | struct pasemi_smbus *smbus = pci_get_drvdata(dev); | 
|  | 397 |  | 
|  | 398 | i2c_del_adapter(&smbus->adapter); | 
|  | 399 | release_region(smbus->base, smbus->size); | 
|  | 400 | kfree(smbus); | 
|  | 401 | } | 
|  | 402 |  | 
|  | 403 | static struct pci_device_id pasemi_smb_ids[] = { | 
|  | 404 | { PCI_DEVICE(0x1959, 0xa003) }, | 
|  | 405 | { 0, } | 
|  | 406 | }; | 
|  | 407 |  | 
|  | 408 | MODULE_DEVICE_TABLE(pci, pasemi_smb_ids); | 
|  | 409 |  | 
|  | 410 | static struct pci_driver pasemi_smb_driver = { | 
|  | 411 | .name		= "i2c-pasemi", | 
|  | 412 | .id_table	= pasemi_smb_ids, | 
|  | 413 | .probe		= pasemi_smb_probe, | 
|  | 414 | .remove		= __devexit_p(pasemi_smb_remove), | 
|  | 415 | }; | 
|  | 416 |  | 
|  | 417 | static int __init pasemi_smb_init(void) | 
|  | 418 | { | 
|  | 419 | return pci_register_driver(&pasemi_smb_driver); | 
|  | 420 | } | 
|  | 421 |  | 
|  | 422 | static void __exit pasemi_smb_exit(void) | 
|  | 423 | { | 
|  | 424 | pci_unregister_driver(&pasemi_smb_driver); | 
|  | 425 | } | 
|  | 426 |  | 
|  | 427 | MODULE_LICENSE("GPL"); | 
|  | 428 | MODULE_AUTHOR ("Olof Johansson <olof@lixom.net>"); | 
|  | 429 | MODULE_DESCRIPTION("PA Semi PWRficient SMBus driver"); | 
|  | 430 |  | 
|  | 431 | module_init(pasemi_smb_init); | 
|  | 432 | module_exit(pasemi_smb_exit); |