672 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (C) 2010-2013 Bluecherry, LLC <https://www.bluecherrydvr.com>4 *5 * Original author:6 * Ben Collins <bcollins@ubuntu.com>7 *8 * Additional work by:9 * John Brooks <john.brooks@bluecherry.net>10 */11 12#include <linux/kernel.h>13#include <linux/module.h>14#include <linux/pci.h>15#include <linux/interrupt.h>16#include <linux/videodev2.h>17#include <linux/delay.h>18#include <linux/sysfs.h>19#include <linux/ktime.h>20#include <linux/slab.h>21 22#include "solo6x10.h"23#include "solo6x10-tw28.h"24 25MODULE_DESCRIPTION("Softlogic 6x10 MPEG4/H.264/G.723 CODEC V4L2/ALSA Driver");26MODULE_AUTHOR("Bluecherry <maintainers@bluecherrydvr.com>");27MODULE_VERSION(SOLO6X10_VERSION);28MODULE_LICENSE("GPL");29 30static unsigned video_nr = -1;31module_param(video_nr, uint, 0644);32MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect (default)");33 34static int full_eeprom; /* default is only top 64B */35module_param(full_eeprom, uint, 0644);36MODULE_PARM_DESC(full_eeprom, "Allow access to full 128B EEPROM (dangerous)");37 38 39static void solo_set_time(struct solo_dev *solo_dev)40{41 struct timespec64 ts;42 43 ktime_get_ts64(&ts);44 45 /* no overflow because we use monotonic timestamps */46 solo_reg_write(solo_dev, SOLO_TIMER_SEC, (u32)ts.tv_sec);47 solo_reg_write(solo_dev, SOLO_TIMER_USEC, (u32)ts.tv_nsec / NSEC_PER_USEC);48}49 50static void solo_timer_sync(struct solo_dev *solo_dev)51{52 u32 sec, usec;53 struct timespec64 ts;54 long diff;55 56 if (solo_dev->type != SOLO_DEV_6110)57 return;58 59 if (++solo_dev->time_sync < 60)60 return;61 62 solo_dev->time_sync = 0;63 64 sec = solo_reg_read(solo_dev, SOLO_TIMER_SEC);65 usec = solo_reg_read(solo_dev, SOLO_TIMER_USEC);66 67 ktime_get_ts64(&ts);68 69 diff = (s32)ts.tv_sec - (s32)sec;70 diff = (diff * 1000000)71 + ((s32)(ts.tv_nsec / NSEC_PER_USEC) - (s32)usec);72 73 if (diff > 1000 || diff < -1000) {74 solo_set_time(solo_dev);75 } else if (diff) {76 long usec_lsb = solo_dev->usec_lsb;77 78 usec_lsb -= diff / 4;79 if (usec_lsb < 0)80 usec_lsb = 0;81 else if (usec_lsb > 255)82 usec_lsb = 255;83 84 solo_dev->usec_lsb = usec_lsb;85 solo_reg_write(solo_dev, SOLO_TIMER_USEC_LSB,86 solo_dev->usec_lsb);87 }88}89 90static irqreturn_t solo_isr(int irq, void *data)91{92 struct solo_dev *solo_dev = data;93 u32 status;94 int i;95 96 status = solo_reg_read(solo_dev, SOLO_IRQ_STAT);97 if (!status)98 return IRQ_NONE;99 100 /* Acknowledge all interrupts immediately */101 solo_reg_write(solo_dev, SOLO_IRQ_STAT, status);102 103 if (status & SOLO_IRQ_PCI_ERR)104 solo_p2m_error_isr(solo_dev);105 106 for (i = 0; i < SOLO_NR_P2M; i++)107 if (status & SOLO_IRQ_P2M(i))108 solo_p2m_isr(solo_dev, i);109 110 if (status & SOLO_IRQ_IIC)111 solo_i2c_isr(solo_dev);112 113 if (status & SOLO_IRQ_VIDEO_IN) {114 solo_video_in_isr(solo_dev);115 solo_timer_sync(solo_dev);116 }117 118 if (status & SOLO_IRQ_ENCODER)119 solo_enc_v4l2_isr(solo_dev);120 121 if (status & SOLO_IRQ_G723)122 solo_g723_isr(solo_dev);123 124 return IRQ_HANDLED;125}126 127static void free_solo_dev(struct solo_dev *solo_dev)128{129 struct pci_dev *pdev = solo_dev->pdev;130 131 if (solo_dev->dev.parent)132 device_unregister(&solo_dev->dev);133 134 if (solo_dev->reg_base) {135 /* Bring down the sub-devices first */136 solo_g723_exit(solo_dev);137 solo_enc_v4l2_exit(solo_dev);138 solo_enc_exit(solo_dev);139 solo_v4l2_exit(solo_dev);140 solo_disp_exit(solo_dev);141 solo_gpio_exit(solo_dev);142 solo_p2m_exit(solo_dev);143 solo_i2c_exit(solo_dev);144 145 /* Now cleanup the PCI device */146 solo_irq_off(solo_dev, ~0);147 }148 149 pci_disable_device(pdev);150 v4l2_device_unregister(&solo_dev->v4l2_dev);151 pci_set_drvdata(pdev, NULL);152 153 kfree(solo_dev);154}155 156static ssize_t eeprom_store(struct device *dev, struct device_attribute *attr,157 const char *buf, size_t count)158{159 struct solo_dev *solo_dev =160 container_of(dev, struct solo_dev, dev);161 u16 *p = (u16 *)buf;162 int i;163 164 if (count & 0x1)165 dev_warn(dev, "EEPROM Write not aligned (truncating)\n");166 167 if (!full_eeprom && count > 64) {168 dev_warn(dev, "EEPROM Write truncated to 64 bytes\n");169 count = 64;170 } else if (full_eeprom && count > 128) {171 dev_warn(dev, "EEPROM Write truncated to 128 bytes\n");172 count = 128;173 }174 175 solo_eeprom_ewen(solo_dev, 1);176 177 for (i = full_eeprom ? 0 : 32; i < min((int)(full_eeprom ? 64 : 32),178 (int)(count / 2)); i++)179 solo_eeprom_write(solo_dev, i, cpu_to_be16(p[i]));180 181 solo_eeprom_ewen(solo_dev, 0);182 183 return count;184}185 186static ssize_t eeprom_show(struct device *dev, struct device_attribute *attr,187 char *buf)188{189 struct solo_dev *solo_dev =190 container_of(dev, struct solo_dev, dev);191 u16 *p = (u16 *)buf;192 int count = (full_eeprom ? 128 : 64);193 int i;194 195 for (i = (full_eeprom ? 0 : 32); i < (count / 2); i++)196 p[i] = be16_to_cpu(solo_eeprom_read(solo_dev, i));197 198 return count;199}200 201static ssize_t p2m_timeouts_show(struct device *dev,202 struct device_attribute *attr,203 char *buf)204{205 struct solo_dev *solo_dev =206 container_of(dev, struct solo_dev, dev);207 208 return sprintf(buf, "%d\n", solo_dev->p2m_timeouts);209}210 211static ssize_t sdram_size_show(struct device *dev,212 struct device_attribute *attr,213 char *buf)214{215 struct solo_dev *solo_dev =216 container_of(dev, struct solo_dev, dev);217 218 return sprintf(buf, "%dMegs\n", solo_dev->sdram_size >> 20);219}220 221static ssize_t tw28xx_show(struct device *dev,222 struct device_attribute *attr,223 char *buf)224{225 struct solo_dev *solo_dev =226 container_of(dev, struct solo_dev, dev);227 228 return sprintf(buf, "tw2815[%d] tw2864[%d] tw2865[%d]\n",229 hweight32(solo_dev->tw2815),230 hweight32(solo_dev->tw2864),231 hweight32(solo_dev->tw2865));232}233 234static ssize_t input_map_show(struct device *dev,235 struct device_attribute *attr,236 char *buf)237{238 struct solo_dev *solo_dev =239 container_of(dev, struct solo_dev, dev);240 unsigned int val;241 char *out = buf;242 243 val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_0);244 out += sprintf(out, "Channel 0 => Input %d\n", val & 0x1f);245 out += sprintf(out, "Channel 1 => Input %d\n", (val >> 5) & 0x1f);246 out += sprintf(out, "Channel 2 => Input %d\n", (val >> 10) & 0x1f);247 out += sprintf(out, "Channel 3 => Input %d\n", (val >> 15) & 0x1f);248 out += sprintf(out, "Channel 4 => Input %d\n", (val >> 20) & 0x1f);249 out += sprintf(out, "Channel 5 => Input %d\n", (val >> 25) & 0x1f);250 251 val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_1);252 out += sprintf(out, "Channel 6 => Input %d\n", val & 0x1f);253 out += sprintf(out, "Channel 7 => Input %d\n", (val >> 5) & 0x1f);254 out += sprintf(out, "Channel 8 => Input %d\n", (val >> 10) & 0x1f);255 out += sprintf(out, "Channel 9 => Input %d\n", (val >> 15) & 0x1f);256 out += sprintf(out, "Channel 10 => Input %d\n", (val >> 20) & 0x1f);257 out += sprintf(out, "Channel 11 => Input %d\n", (val >> 25) & 0x1f);258 259 val = solo_reg_read(solo_dev, SOLO_VI_CH_SWITCH_2);260 out += sprintf(out, "Channel 12 => Input %d\n", val & 0x1f);261 out += sprintf(out, "Channel 13 => Input %d\n", (val >> 5) & 0x1f);262 out += sprintf(out, "Channel 14 => Input %d\n", (val >> 10) & 0x1f);263 out += sprintf(out, "Channel 15 => Input %d\n", (val >> 15) & 0x1f);264 out += sprintf(out, "Spot Output => Input %d\n", (val >> 20) & 0x1f);265 266 return out - buf;267}268 269static ssize_t p2m_timeout_store(struct device *dev,270 struct device_attribute *attr,271 const char *buf, size_t count)272{273 struct solo_dev *solo_dev =274 container_of(dev, struct solo_dev, dev);275 unsigned long ms;276 int ret = kstrtoul(buf, 10, &ms);277 278 if (ret < 0 || ms > 200)279 return -EINVAL;280 solo_dev->p2m_jiffies = msecs_to_jiffies(ms);281 282 return count;283}284 285static ssize_t p2m_timeout_show(struct device *dev,286 struct device_attribute *attr,287 char *buf)288{289 struct solo_dev *solo_dev =290 container_of(dev, struct solo_dev, dev);291 292 return sprintf(buf, "%ums\n", jiffies_to_msecs(solo_dev->p2m_jiffies));293}294 295static ssize_t intervals_show(struct device *dev,296 struct device_attribute *attr,297 char *buf)298{299 struct solo_dev *solo_dev =300 container_of(dev, struct solo_dev, dev);301 char *out = buf;302 int fps = solo_dev->fps;303 int i;304 305 for (i = 0; i < solo_dev->nr_chans; i++) {306 out += sprintf(out, "Channel %d: %d/%d (0x%08x)\n",307 i, solo_dev->v4l2_enc[i]->interval, fps,308 solo_reg_read(solo_dev, SOLO_CAP_CH_INTV(i)));309 }310 311 return out - buf;312}313 314static ssize_t sdram_offsets_show(struct device *dev,315 struct device_attribute *attr,316 char *buf)317{318 struct solo_dev *solo_dev =319 container_of(dev, struct solo_dev, dev);320 char *out = buf;321 322 out += sprintf(out, "DISP: 0x%08x @ 0x%08x\n",323 SOLO_DISP_EXT_ADDR,324 SOLO_DISP_EXT_SIZE);325 326 out += sprintf(out, "EOSD: 0x%08x @ 0x%08x (0x%08x * %d)\n",327 SOLO_EOSD_EXT_ADDR,328 SOLO_EOSD_EXT_AREA(solo_dev),329 SOLO_EOSD_EXT_SIZE(solo_dev),330 SOLO_EOSD_EXT_AREA(solo_dev) /331 SOLO_EOSD_EXT_SIZE(solo_dev));332 333 out += sprintf(out, "MOTI: 0x%08x @ 0x%08x\n",334 SOLO_MOTION_EXT_ADDR(solo_dev),335 SOLO_MOTION_EXT_SIZE);336 337 out += sprintf(out, "G723: 0x%08x @ 0x%08x\n",338 SOLO_G723_EXT_ADDR(solo_dev),339 SOLO_G723_EXT_SIZE);340 341 out += sprintf(out, "CAPT: 0x%08x @ 0x%08x (0x%08x * %d)\n",342 SOLO_CAP_EXT_ADDR(solo_dev),343 SOLO_CAP_EXT_SIZE(solo_dev),344 SOLO_CAP_PAGE_SIZE,345 SOLO_CAP_EXT_SIZE(solo_dev) / SOLO_CAP_PAGE_SIZE);346 347 out += sprintf(out, "EREF: 0x%08x @ 0x%08x (0x%08x * %d)\n",348 SOLO_EREF_EXT_ADDR(solo_dev),349 SOLO_EREF_EXT_AREA(solo_dev),350 SOLO_EREF_EXT_SIZE,351 SOLO_EREF_EXT_AREA(solo_dev) / SOLO_EREF_EXT_SIZE);352 353 out += sprintf(out, "MPEG: 0x%08x @ 0x%08x\n",354 SOLO_MP4E_EXT_ADDR(solo_dev),355 SOLO_MP4E_EXT_SIZE(solo_dev));356 357 out += sprintf(out, "JPEG: 0x%08x @ 0x%08x\n",358 SOLO_JPEG_EXT_ADDR(solo_dev),359 SOLO_JPEG_EXT_SIZE(solo_dev));360 361 return out - buf;362}363 364static ssize_t sdram_show(struct file *file, struct kobject *kobj,365 struct bin_attribute *a, char *buf,366 loff_t off, size_t count)367{368 struct device *dev = kobj_to_dev(kobj);369 struct solo_dev *solo_dev =370 container_of(dev, struct solo_dev, dev);371 const int size = solo_dev->sdram_size;372 373 if (off >= size)374 return 0;375 376 if (off + count > size)377 count = size - off;378 379 if (solo_p2m_dma(solo_dev, 0, buf, off, count, 0, 0))380 return -EIO;381 382 return count;383}384 385static const struct device_attribute solo_dev_attrs[] = {386 __ATTR(eeprom, 0640, eeprom_show, eeprom_store),387 __ATTR(p2m_timeout, 0644, p2m_timeout_show, p2m_timeout_store),388 __ATTR_RO(p2m_timeouts),389 __ATTR_RO(sdram_size),390 __ATTR_RO(tw28xx),391 __ATTR_RO(input_map),392 __ATTR_RO(intervals),393 __ATTR_RO(sdram_offsets),394};395 396static void solo_device_release(struct device *dev)397{398 /* Do nothing */399}400 401static int solo_sysfs_init(struct solo_dev *solo_dev)402{403 struct bin_attribute *sdram_attr = &solo_dev->sdram_attr;404 struct device *dev = &solo_dev->dev;405 const char *driver;406 int i;407 408 if (solo_dev->type == SOLO_DEV_6110)409 driver = "solo6110";410 else411 driver = "solo6010";412 413 dev->release = solo_device_release;414 dev->parent = &solo_dev->pdev->dev;415 set_dev_node(dev, dev_to_node(&solo_dev->pdev->dev));416 dev_set_name(dev, "%s-%d-%d", driver, solo_dev->vfd->num,417 solo_dev->nr_chans);418 419 if (device_register(dev)) {420 put_device(dev);421 dev->parent = NULL;422 return -ENOMEM;423 }424 425 for (i = 0; i < ARRAY_SIZE(solo_dev_attrs); i++) {426 if (device_create_file(dev, &solo_dev_attrs[i])) {427 device_unregister(dev);428 return -ENOMEM;429 }430 }431 432 sysfs_attr_init(&sdram_attr->attr);433 sdram_attr->attr.name = "sdram";434 sdram_attr->attr.mode = 0440;435 sdram_attr->read = sdram_show;436 sdram_attr->size = solo_dev->sdram_size;437 438 if (device_create_bin_file(dev, sdram_attr)) {439 device_unregister(dev);440 return -ENOMEM;441 }442 443 return 0;444}445 446static int solo_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)447{448 struct solo_dev *solo_dev;449 int ret;450 u8 chip_id;451 452 solo_dev = kzalloc(sizeof(*solo_dev), GFP_KERNEL);453 if (solo_dev == NULL)454 return -ENOMEM;455 456 if (id->driver_data == SOLO_DEV_6010)457 dev_info(&pdev->dev, "Probing Softlogic 6010\n");458 else459 dev_info(&pdev->dev, "Probing Softlogic 6110\n");460 461 solo_dev->type = id->driver_data;462 solo_dev->pdev = pdev;463 ret = v4l2_device_register(&pdev->dev, &solo_dev->v4l2_dev);464 if (ret)465 goto fail_probe;466 467 /* Only for during init */468 solo_dev->p2m_jiffies = msecs_to_jiffies(100);469 470 ret = pci_enable_device(pdev);471 if (ret)472 goto fail_probe;473 474 pci_set_master(pdev);475 476 /* RETRY/TRDY Timeout disabled */477 pci_write_config_byte(pdev, 0x40, 0x00);478 pci_write_config_byte(pdev, 0x41, 0x00);479 480 ret = pcim_iomap_regions(pdev, BIT(0), SOLO6X10_NAME);481 if (ret)482 goto fail_probe;483 solo_dev->reg_base = pcim_iomap_table(pdev)[0];484 485 chip_id = solo_reg_read(solo_dev, SOLO_CHIP_OPTION) &486 SOLO_CHIP_ID_MASK;487 switch (chip_id) {488 case 7:489 solo_dev->nr_chans = 16;490 solo_dev->nr_ext = 5;491 break;492 case 6:493 solo_dev->nr_chans = 8;494 solo_dev->nr_ext = 2;495 break;496 default:497 dev_warn(&pdev->dev, "Invalid chip_id 0x%02x, assuming 4 ch\n",498 chip_id);499 fallthrough;500 case 5:501 solo_dev->nr_chans = 4;502 solo_dev->nr_ext = 1;503 }504 505 /* Disable all interrupts to start */506 solo_irq_off(solo_dev, ~0);507 508 /* Initial global settings */509 if (solo_dev->type == SOLO_DEV_6010) {510 solo_dev->clock_mhz = 108;511 solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT512 | SOLO_SYS_CFG_INPUTDIV(25)513 | SOLO_SYS_CFG_FEEDBACKDIV(solo_dev->clock_mhz * 2 - 2)514 | SOLO_SYS_CFG_OUTDIV(3);515 solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);516 } else {517 u32 divq, divf;518 519 solo_dev->clock_mhz = 135;520 521 if (solo_dev->clock_mhz < 125) {522 divq = 3;523 divf = (solo_dev->clock_mhz * 4) / 3 - 1;524 } else {525 divq = 2;526 divf = (solo_dev->clock_mhz * 2) / 3 - 1;527 }528 529 solo_reg_write(solo_dev, SOLO_PLL_CONFIG,530 (1 << 20) | /* PLL_RANGE */531 (8 << 15) | /* PLL_DIVR */532 (divq << 12) |533 (divf << 4) |534 (1 << 1) /* PLL_FSEN */);535 536 solo_dev->sys_config = SOLO_SYS_CFG_SDRAM64BIT;537 }538 539 solo_reg_write(solo_dev, SOLO_SYS_CFG, solo_dev->sys_config);540 solo_reg_write(solo_dev, SOLO_TIMER_CLOCK_NUM,541 solo_dev->clock_mhz - 1);542 543 /* PLL locking time of 1ms */544 mdelay(1);545 546 ret = devm_request_irq(&pdev->dev, pdev->irq, solo_isr, IRQF_SHARED,547 SOLO6X10_NAME, solo_dev);548 if (ret)549 goto fail_probe;550 551 /* Handle this from the start */552 solo_irq_on(solo_dev, SOLO_IRQ_PCI_ERR);553 554 ret = solo_i2c_init(solo_dev);555 if (ret)556 goto fail_probe;557 558 /* Setup the DMA engine */559 solo_reg_write(solo_dev, SOLO_DMA_CTRL,560 SOLO_DMA_CTRL_REFRESH_CYCLE(1) |561 SOLO_DMA_CTRL_SDRAM_SIZE(2) |562 SOLO_DMA_CTRL_SDRAM_CLK_INVERT |563 SOLO_DMA_CTRL_READ_CLK_SELECT |564 SOLO_DMA_CTRL_LATENCY(1));565 566 /* Undocumented crap */567 solo_reg_write(solo_dev, SOLO_DMA_CTRL1,568 solo_dev->type == SOLO_DEV_6010 ? 0x100 : 0x300);569 570 if (solo_dev->type != SOLO_DEV_6010) {571 solo_dev->usec_lsb = 0x3f;572 solo_set_time(solo_dev);573 }574 575 /* Disable watchdog */576 solo_reg_write(solo_dev, SOLO_WATCHDOG, 0);577 578 /* Initialize sub components */579 580 ret = solo_p2m_init(solo_dev);581 if (ret)582 goto fail_probe;583 584 ret = solo_disp_init(solo_dev);585 if (ret)586 goto fail_probe;587 588 ret = solo_gpio_init(solo_dev);589 if (ret)590 goto fail_probe;591 592 ret = solo_tw28_init(solo_dev);593 if (ret)594 goto fail_probe;595 596 ret = solo_v4l2_init(solo_dev, video_nr);597 if (ret)598 goto fail_probe;599 600 ret = solo_enc_init(solo_dev);601 if (ret)602 goto fail_probe;603 604 ret = solo_enc_v4l2_init(solo_dev, video_nr);605 if (ret)606 goto fail_probe;607 608 ret = solo_g723_init(solo_dev);609 if (ret)610 goto fail_probe;611 612 ret = solo_sysfs_init(solo_dev);613 if (ret)614 goto fail_probe;615 616 /* Now that init is over, set this lower */617 solo_dev->p2m_jiffies = msecs_to_jiffies(20);618 619 return 0;620 621fail_probe:622 free_solo_dev(solo_dev);623 return ret;624}625 626static void solo_pci_remove(struct pci_dev *pdev)627{628 struct v4l2_device *v4l2_dev = pci_get_drvdata(pdev);629 struct solo_dev *solo_dev = container_of(v4l2_dev, struct solo_dev, v4l2_dev);630 631 free_solo_dev(solo_dev);632}633 634static const struct pci_device_id solo_id_table[] = {635 /* 6010 based cards */636 { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6010),637 .driver_data = SOLO_DEV_6010 },638 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_4),639 .driver_data = SOLO_DEV_6010 },640 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_9),641 .driver_data = SOLO_DEV_6010 },642 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_NEUSOLO_16),643 .driver_data = SOLO_DEV_6010 },644 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_4),645 .driver_data = SOLO_DEV_6010 },646 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_9),647 .driver_data = SOLO_DEV_6010 },648 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_SOLO_16),649 .driver_data = SOLO_DEV_6010 },650 /* 6110 based cards */651 { PCI_DEVICE(PCI_VENDOR_ID_SOFTLOGIC, PCI_DEVICE_ID_SOLO6110),652 .driver_data = SOLO_DEV_6110 },653 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_4),654 .driver_data = SOLO_DEV_6110 },655 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_8),656 .driver_data = SOLO_DEV_6110 },657 { PCI_DEVICE(PCI_VENDOR_ID_BLUECHERRY, PCI_DEVICE_ID_BC_6110_16),658 .driver_data = SOLO_DEV_6110 },659 {0,}660};661 662MODULE_DEVICE_TABLE(pci, solo_id_table);663 664static struct pci_driver solo_pci_driver = {665 .name = SOLO6X10_NAME,666 .id_table = solo_id_table,667 .probe = solo_pci_probe,668 .remove = solo_pci_remove,669};670 671module_pci_driver(solo_pci_driver);672