brintos

brintos / linux-shallow public Read only

0
0
Text · 17.0 KiB · 1a9e2bc Raw
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