| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * comedi/drivers/jr3_pci.c |
| * hardware driver for JR3/PCI force sensor board |
| * |
| * COMEDI - Linux Control and Measurement Device Interface |
| * Copyright (C) 2007 Anders Blomdell <anders.blomdell@control.lth.se> |
| */ |
| /* |
| * Driver: jr3_pci |
| * Description: JR3/PCI force sensor board |
| * Author: Anders Blomdell <anders.blomdell@control.lth.se> |
| * Updated: Thu, 01 Nov 2012 17:34:55 +0000 |
| * Status: works |
| * Devices: [JR3] PCI force sensor board (jr3_pci) |
| * |
| * Configuration options: |
| * None |
| * |
| * Manual configuration of comedi devices is not supported by this |
| * driver; supported PCI devices are configured as comedi devices |
| * automatically. |
| * |
| * The DSP on the board requires initialization code, which can be |
| * loaded by placing it in /lib/firmware/comedi. The initialization |
| * code should be somewhere on the media you got with your card. One |
| * version is available from https://www.comedi.org in the |
| * comedi_nonfree_firmware tarball. The file is called "jr3pci.idm". |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/delay.h> |
| #include <linux/ctype.h> |
| #include <linux/jiffies.h> |
| #include <linux/slab.h> |
| #include <linux/timer.h> |
| |
| #include "../comedi_pci.h" |
| |
| #include "jr3_pci.h" |
| |
| #define PCI_VENDOR_ID_JR3 0x1762 |
| |
| enum jr3_pci_boardid { |
| BOARD_JR3_1, |
| BOARD_JR3_2, |
| BOARD_JR3_3, |
| BOARD_JR3_4, |
| }; |
| |
| struct jr3_pci_board { |
| const char *name; |
| int n_subdevs; |
| }; |
| |
| static const struct jr3_pci_board jr3_pci_boards[] = { |
| [BOARD_JR3_1] = { |
| .name = "jr3_pci_1", |
| .n_subdevs = 1, |
| }, |
| [BOARD_JR3_2] = { |
| .name = "jr3_pci_2", |
| .n_subdevs = 2, |
| }, |
| [BOARD_JR3_3] = { |
| .name = "jr3_pci_3", |
| .n_subdevs = 3, |
| }, |
| [BOARD_JR3_4] = { |
| .name = "jr3_pci_4", |
| .n_subdevs = 4, |
| }, |
| }; |
| |
| struct jr3_pci_transform { |
| struct { |
| u16 link_type; |
| s16 link_amount; |
| } link[8]; |
| }; |
| |
| struct jr3_pci_poll_delay { |
| int min; |
| int max; |
| }; |
| |
| struct jr3_pci_dev_private { |
| struct timer_list timer; |
| struct comedi_device *dev; |
| }; |
| |
| union jr3_pci_single_range { |
| struct comedi_lrange l; |
| char _reserved[offsetof(struct comedi_lrange, range[1])]; |
| }; |
| |
| enum jr3_pci_poll_state { |
| state_jr3_poll, |
| state_jr3_init_wait_for_offset, |
| state_jr3_init_transform_complete, |
| state_jr3_init_set_full_scale_complete, |
| state_jr3_init_use_offset_complete, |
| state_jr3_done |
| }; |
| |
| struct jr3_pci_subdev_private { |
| struct jr3_sensor __iomem *sensor; |
| unsigned long next_time_min; |
| enum jr3_pci_poll_state state; |
| int serial_no; |
| int model_no; |
| union jr3_pci_single_range range[9]; |
| const struct comedi_lrange *range_table_list[8 * 7 + 2]; |
| unsigned int maxdata_list[8 * 7 + 2]; |
| u16 errors; |
| int retries; |
| }; |
| |
| static struct jr3_pci_poll_delay poll_delay_min_max(int min, int max) |
| { |
| struct jr3_pci_poll_delay result; |
| |
| result.min = min; |
| result.max = max; |
| return result; |
| } |
| |
| static int is_complete(struct jr3_sensor __iomem *sensor) |
| { |
| return get_s16(&sensor->command_word0) == 0; |
| } |
| |
| static void set_transforms(struct jr3_sensor __iomem *sensor, |
| const struct jr3_pci_transform *transf, short num) |
| { |
| int i; |
| |
| num &= 0x000f; /* Make sure that 0 <= num <= 15 */ |
| for (i = 0; i < 8; i++) { |
| set_u16(&sensor->transforms[num].link[i].link_type, |
| transf->link[i].link_type); |
| udelay(1); |
| set_s16(&sensor->transforms[num].link[i].link_amount, |
| transf->link[i].link_amount); |
| udelay(1); |
| if (transf->link[i].link_type == end_x_form) |
| break; |
| } |
| } |
| |
| static void use_transform(struct jr3_sensor __iomem *sensor, |
| short transf_num) |
| { |
| set_s16(&sensor->command_word0, 0x0500 + (transf_num & 0x000f)); |
| } |
| |
| static void use_offset(struct jr3_sensor __iomem *sensor, short offset_num) |
| { |
| set_s16(&sensor->command_word0, 0x0600 + (offset_num & 0x000f)); |
| } |
| |
| static void set_offset(struct jr3_sensor __iomem *sensor) |
| { |
| set_s16(&sensor->command_word0, 0x0700); |
| } |
| |
| struct six_axis_t { |
| s16 fx; |
| s16 fy; |
| s16 fz; |
| s16 mx; |
| s16 my; |
| s16 mz; |
| }; |
| |
| static void set_full_scales(struct jr3_sensor __iomem *sensor, |
| struct six_axis_t full_scale) |
| { |
| set_s16(&sensor->full_scale.fx, full_scale.fx); |
| set_s16(&sensor->full_scale.fy, full_scale.fy); |
| set_s16(&sensor->full_scale.fz, full_scale.fz); |
| set_s16(&sensor->full_scale.mx, full_scale.mx); |
| set_s16(&sensor->full_scale.my, full_scale.my); |
| set_s16(&sensor->full_scale.mz, full_scale.mz); |
| set_s16(&sensor->command_word0, 0x0a00); |
| } |
| |
| static struct six_axis_t get_max_full_scales(struct jr3_sensor __iomem *sensor) |
| { |
| struct six_axis_t result; |
| |
| result.fx = get_s16(&sensor->max_full_scale.fx); |
| result.fy = get_s16(&sensor->max_full_scale.fy); |
| result.fz = get_s16(&sensor->max_full_scale.fz); |
| result.mx = get_s16(&sensor->max_full_scale.mx); |
| result.my = get_s16(&sensor->max_full_scale.my); |
| result.mz = get_s16(&sensor->max_full_scale.mz); |
| return result; |
| } |
| |
| static unsigned int jr3_pci_ai_read_chan(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| unsigned int chan) |
| { |
| struct jr3_pci_subdev_private *spriv = s->private; |
| unsigned int val = 0; |
| |
| if (spriv->state != state_jr3_done) |
| return 0; |
| |
| if (chan < 56) { |
| unsigned int axis = chan % 8; |
| unsigned int filter = chan / 8; |
| |
| switch (axis) { |
| case 0: |
| val = get_s16(&spriv->sensor->filter[filter].fx); |
| break; |
| case 1: |
| val = get_s16(&spriv->sensor->filter[filter].fy); |
| break; |
| case 2: |
| val = get_s16(&spriv->sensor->filter[filter].fz); |
| break; |
| case 3: |
| val = get_s16(&spriv->sensor->filter[filter].mx); |
| break; |
| case 4: |
| val = get_s16(&spriv->sensor->filter[filter].my); |
| break; |
| case 5: |
| val = get_s16(&spriv->sensor->filter[filter].mz); |
| break; |
| case 6: |
| val = get_s16(&spriv->sensor->filter[filter].v1); |
| break; |
| case 7: |
| val = get_s16(&spriv->sensor->filter[filter].v2); |
| break; |
| } |
| val += 0x4000; |
| } else if (chan == 56) { |
| val = get_u16(&spriv->sensor->model_no); |
| } else if (chan == 57) { |
| val = get_u16(&spriv->sensor->serial_no); |
| } |
| |
| return val; |
| } |
| |
| static int jr3_pci_ai_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, |
| unsigned int *data) |
| { |
| struct jr3_pci_subdev_private *spriv = s->private; |
| unsigned int chan = CR_CHAN(insn->chanspec); |
| u16 errors; |
| int i; |
| |
| errors = get_u16(&spriv->sensor->errors); |
| if (spriv->state != state_jr3_done || |
| (errors & (watch_dog | watch_dog2 | sensor_change))) { |
| /* No sensor or sensor changed */ |
| if (spriv->state == state_jr3_done) { |
| /* Restart polling */ |
| spriv->state = state_jr3_poll; |
| } |
| return -EAGAIN; |
| } |
| |
| for (i = 0; i < insn->n; i++) |
| data[i] = jr3_pci_ai_read_chan(dev, s, chan); |
| |
| return insn->n; |
| } |
| |
| static int jr3_pci_open(struct comedi_device *dev) |
| { |
| struct jr3_pci_subdev_private *spriv; |
| struct comedi_subdevice *s; |
| int i; |
| |
| for (i = 0; i < dev->n_subdevices; i++) { |
| s = &dev->subdevices[i]; |
| spriv = s->private; |
| dev_dbg(dev->class_dev, "serial[%d]: %d\n", s->index, |
| spriv->serial_no); |
| } |
| return 0; |
| } |
| |
| static int read_idm_word(const u8 *data, size_t size, int *pos, |
| unsigned int *val) |
| { |
| int result = 0; |
| int value; |
| |
| if (pos && val) { |
| /* Skip over non hex */ |
| for (; *pos < size && !isxdigit(data[*pos]); (*pos)++) |
| ; |
| /* Collect value */ |
| *val = 0; |
| for (; *pos < size; (*pos)++) { |
| value = hex_to_bin(data[*pos]); |
| if (value >= 0) { |
| result = 1; |
| *val = (*val << 4) + value; |
| } else { |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| static int jr3_check_firmware(struct comedi_device *dev, |
| const u8 *data, size_t size) |
| { |
| int more = 1; |
| int pos = 0; |
| |
| /* |
| * IDM file format is: |
| * { count, address, data <count> } * |
| * ffff |
| */ |
| while (more) { |
| unsigned int count = 0; |
| unsigned int addr = 0; |
| |
| more = more && read_idm_word(data, size, &pos, &count); |
| if (more && count == 0xffff) |
| return 0; |
| |
| more = more && read_idm_word(data, size, &pos, &addr); |
| while (more && count > 0) { |
| unsigned int dummy = 0; |
| |
| more = more && read_idm_word(data, size, &pos, &dummy); |
| count--; |
| } |
| } |
| |
| return -ENODATA; |
| } |
| |
| static void jr3_write_firmware(struct comedi_device *dev, |
| int subdev, const u8 *data, size_t size) |
| { |
| struct jr3_block __iomem *block = dev->mmio; |
| u32 __iomem *lo; |
| u32 __iomem *hi; |
| int more = 1; |
| int pos = 0; |
| |
| while (more) { |
| unsigned int count = 0; |
| unsigned int addr = 0; |
| |
| more = more && read_idm_word(data, size, &pos, &count); |
| if (more && count == 0xffff) |
| return; |
| |
| more = more && read_idm_word(data, size, &pos, &addr); |
| |
| dev_dbg(dev->class_dev, "Loading#%d %4.4x bytes at %4.4x\n", |
| subdev, count, addr); |
| |
| while (more && count > 0) { |
| if (addr & 0x4000) { |
| /* 16 bit data, never seen in real life!! */ |
| unsigned int data1 = 0; |
| |
| more = more && |
| read_idm_word(data, size, &pos, &data1); |
| count--; |
| /* jr3[addr + 0x20000 * pnum] = data1; */ |
| } else { |
| /* Download 24 bit program */ |
| unsigned int data1 = 0; |
| unsigned int data2 = 0; |
| |
| lo = &block[subdev].program_lo[addr]; |
| hi = &block[subdev].program_hi[addr]; |
| |
| more = more && |
| read_idm_word(data, size, &pos, &data1); |
| more = more && |
| read_idm_word(data, size, &pos, &data2); |
| count -= 2; |
| if (more) { |
| set_u16(lo, data1); |
| udelay(1); |
| set_u16(hi, data2); |
| udelay(1); |
| } |
| } |
| addr++; |
| } |
| } |
| } |
| |
| static int jr3_download_firmware(struct comedi_device *dev, |
| const u8 *data, size_t size, |
| unsigned long context) |
| { |
| int subdev; |
| int ret; |
| |
| /* verify IDM file format */ |
| ret = jr3_check_firmware(dev, data, size); |
| if (ret) |
| return ret; |
| |
| /* write firmware to each subdevice */ |
| for (subdev = 0; subdev < dev->n_subdevices; subdev++) |
| jr3_write_firmware(dev, subdev, data, size); |
| |
| return 0; |
| } |
| |
| static struct jr3_pci_poll_delay |
| jr3_pci_poll_subdevice(struct comedi_subdevice *s) |
| { |
| struct jr3_pci_subdev_private *spriv = s->private; |
| struct jr3_pci_poll_delay result = poll_delay_min_max(1000, 2000); |
| struct jr3_sensor __iomem *sensor; |
| u16 model_no; |
| u16 serial_no; |
| int errors; |
| int i; |
| |
| sensor = spriv->sensor; |
| errors = get_u16(&sensor->errors); |
| |
| if (errors != spriv->errors) |
| spriv->errors = errors; |
| |
| /* Sensor communication lost? force poll mode */ |
| if (errors & (watch_dog | watch_dog2 | sensor_change)) |
| spriv->state = state_jr3_poll; |
| |
| switch (spriv->state) { |
| case state_jr3_poll: |
| model_no = get_u16(&sensor->model_no); |
| serial_no = get_u16(&sensor->serial_no); |
| |
| if ((errors & (watch_dog | watch_dog2)) || |
| model_no == 0 || serial_no == 0) { |
| /* |
| * Still no sensor, keep on polling. |
| * Since it takes up to 10 seconds for offsets to |
| * stabilize, polling each second should suffice. |
| */ |
| } else { |
| spriv->retries = 0; |
| spriv->state = state_jr3_init_wait_for_offset; |
| } |
| break; |
| case state_jr3_init_wait_for_offset: |
| spriv->retries++; |
| if (spriv->retries < 10) { |
| /* |
| * Wait for offeset to stabilize |
| * (< 10 s according to manual) |
| */ |
| } else { |
| struct jr3_pci_transform transf; |
| |
| spriv->model_no = get_u16(&sensor->model_no); |
| spriv->serial_no = get_u16(&sensor->serial_no); |
| |
| /* Transformation all zeros */ |
| for (i = 0; i < ARRAY_SIZE(transf.link); i++) { |
| transf.link[i].link_type = (enum link_types)0; |
| transf.link[i].link_amount = 0; |
| } |
| |
| set_transforms(sensor, &transf, 0); |
| use_transform(sensor, 0); |
| spriv->state = state_jr3_init_transform_complete; |
| /* Allow 20 ms for completion */ |
| result = poll_delay_min_max(20, 100); |
| } |
| break; |
| case state_jr3_init_transform_complete: |
| if (!is_complete(sensor)) { |
| result = poll_delay_min_max(20, 100); |
| } else { |
| /* Set full scale */ |
| struct six_axis_t max_full_scale; |
| |
| max_full_scale = get_max_full_scales(sensor); |
| set_full_scales(sensor, max_full_scale); |
| |
| spriv->state = state_jr3_init_set_full_scale_complete; |
| /* Allow 20 ms for completion */ |
| result = poll_delay_min_max(20, 100); |
| } |
| break; |
| case state_jr3_init_set_full_scale_complete: |
| if (!is_complete(sensor)) { |
| result = poll_delay_min_max(20, 100); |
| } else { |
| struct force_array __iomem *fs = &sensor->full_scale; |
| union jr3_pci_single_range *r = spriv->range; |
| |
| /* Use ranges in kN or we will overflow around 2000N! */ |
| r[0].l.range[0].min = -get_s16(&fs->fx) * 1000; |
| r[0].l.range[0].max = get_s16(&fs->fx) * 1000; |
| r[1].l.range[0].min = -get_s16(&fs->fy) * 1000; |
| r[1].l.range[0].max = get_s16(&fs->fy) * 1000; |
| r[2].l.range[0].min = -get_s16(&fs->fz) * 1000; |
| r[2].l.range[0].max = get_s16(&fs->fz) * 1000; |
| r[3].l.range[0].min = -get_s16(&fs->mx) * 100; |
| r[3].l.range[0].max = get_s16(&fs->mx) * 100; |
| r[4].l.range[0].min = -get_s16(&fs->my) * 100; |
| r[4].l.range[0].max = get_s16(&fs->my) * 100; |
| r[5].l.range[0].min = -get_s16(&fs->mz) * 100; |
| /* the next five are questionable */ |
| r[5].l.range[0].max = get_s16(&fs->mz) * 100; |
| r[6].l.range[0].min = -get_s16(&fs->v1) * 100; |
| r[6].l.range[0].max = get_s16(&fs->v1) * 100; |
| r[7].l.range[0].min = -get_s16(&fs->v2) * 100; |
| r[7].l.range[0].max = get_s16(&fs->v2) * 100; |
| r[8].l.range[0].min = 0; |
| r[8].l.range[0].max = 65535; |
| |
| use_offset(sensor, 0); |
| spriv->state = state_jr3_init_use_offset_complete; |
| /* Allow 40 ms for completion */ |
| result = poll_delay_min_max(40, 100); |
| } |
| break; |
| case state_jr3_init_use_offset_complete: |
| if (!is_complete(sensor)) { |
| result = poll_delay_min_max(20, 100); |
| } else { |
| set_s16(&sensor->offsets.fx, 0); |
| set_s16(&sensor->offsets.fy, 0); |
| set_s16(&sensor->offsets.fz, 0); |
| set_s16(&sensor->offsets.mx, 0); |
| set_s16(&sensor->offsets.my, 0); |
| set_s16(&sensor->offsets.mz, 0); |
| |
| set_offset(sensor); |
| |
| spriv->state = state_jr3_done; |
| } |
| break; |
| case state_jr3_done: |
| result = poll_delay_min_max(10000, 20000); |
| break; |
| default: |
| break; |
| } |
| |
| return result; |
| } |
| |
| static void jr3_pci_poll_dev(struct timer_list *t) |
| { |
| struct jr3_pci_dev_private *devpriv = from_timer(devpriv, t, timer); |
| struct comedi_device *dev = devpriv->dev; |
| struct jr3_pci_subdev_private *spriv; |
| struct comedi_subdevice *s; |
| unsigned long flags; |
| unsigned long now; |
| int delay; |
| int i; |
| |
| spin_lock_irqsave(&dev->spinlock, flags); |
| delay = 1000; |
| now = jiffies; |
| |
| /* Poll all sensors that are ready to be polled */ |
| for (i = 0; i < dev->n_subdevices; i++) { |
| s = &dev->subdevices[i]; |
| spriv = s->private; |
| |
| if (time_after_eq(now, spriv->next_time_min)) { |
| struct jr3_pci_poll_delay sub_delay; |
| |
| sub_delay = jr3_pci_poll_subdevice(s); |
| |
| spriv->next_time_min = jiffies + |
| msecs_to_jiffies(sub_delay.min); |
| |
| if (sub_delay.max && sub_delay.max < delay) |
| /* |
| * Wake up as late as possible -> |
| * poll as many sensors as possible at once. |
| */ |
| delay = sub_delay.max; |
| } |
| } |
| spin_unlock_irqrestore(&dev->spinlock, flags); |
| |
| devpriv->timer.expires = jiffies + msecs_to_jiffies(delay); |
| add_timer(&devpriv->timer); |
| } |
| |
| static struct jr3_pci_subdev_private * |
| jr3_pci_alloc_spriv(struct comedi_device *dev, struct comedi_subdevice *s) |
| { |
| struct jr3_block __iomem *block = dev->mmio; |
| struct jr3_pci_subdev_private *spriv; |
| int j; |
| int k; |
| |
| spriv = comedi_alloc_spriv(s, sizeof(*spriv)); |
| if (!spriv) |
| return NULL; |
| |
| spriv->sensor = &block[s->index].sensor; |
| |
| for (j = 0; j < 8; j++) { |
| spriv->range[j].l.length = 1; |
| spriv->range[j].l.range[0].min = -1000000; |
| spriv->range[j].l.range[0].max = 1000000; |
| |
| for (k = 0; k < 7; k++) { |
| spriv->range_table_list[j + k * 8] = &spriv->range[j].l; |
| spriv->maxdata_list[j + k * 8] = 0x7fff; |
| } |
| } |
| spriv->range[8].l.length = 1; |
| spriv->range[8].l.range[0].min = 0; |
| spriv->range[8].l.range[0].max = 65535; |
| |
| spriv->range_table_list[56] = &spriv->range[8].l; |
| spriv->range_table_list[57] = &spriv->range[8].l; |
| spriv->maxdata_list[56] = 0xffff; |
| spriv->maxdata_list[57] = 0xffff; |
| |
| return spriv; |
| } |
| |
| static void jr3_pci_show_copyright(struct comedi_device *dev) |
| { |
| struct jr3_block __iomem *block = dev->mmio; |
| struct jr3_sensor __iomem *sensor0 = &block[0].sensor; |
| char copy[ARRAY_SIZE(sensor0->copyright) + 1]; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(sensor0->copyright); i++) |
| copy[i] = (char)(get_u16(&sensor0->copyright[i]) >> 8); |
| copy[i] = '\0'; |
| dev_dbg(dev->class_dev, "Firmware copyright: %s\n", copy); |
| } |
| |
| static int jr3_pci_auto_attach(struct comedi_device *dev, |
| unsigned long context) |
| { |
| struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
| static const struct jr3_pci_board *board; |
| struct jr3_pci_dev_private *devpriv; |
| struct jr3_pci_subdev_private *spriv; |
| struct jr3_block __iomem *block; |
| struct comedi_subdevice *s; |
| int ret; |
| int i; |
| |
| BUILD_BUG_ON(sizeof(struct jr3_block) != 0x80000); |
| |
| if (context < ARRAY_SIZE(jr3_pci_boards)) |
| board = &jr3_pci_boards[context]; |
| if (!board) |
| return -ENODEV; |
| dev->board_ptr = board; |
| dev->board_name = board->name; |
| |
| devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); |
| if (!devpriv) |
| return -ENOMEM; |
| |
| ret = comedi_pci_enable(dev); |
| if (ret) |
| return ret; |
| |
| if (pci_resource_len(pcidev, 0) < board->n_subdevs * sizeof(*block)) |
| return -ENXIO; |
| |
| dev->mmio = pci_ioremap_bar(pcidev, 0); |
| if (!dev->mmio) |
| return -ENOMEM; |
| |
| block = dev->mmio; |
| |
| ret = comedi_alloc_subdevices(dev, board->n_subdevs); |
| if (ret) |
| return ret; |
| |
| dev->open = jr3_pci_open; |
| for (i = 0; i < dev->n_subdevices; i++) { |
| s = &dev->subdevices[i]; |
| s->type = COMEDI_SUBD_AI; |
| s->subdev_flags = SDF_READABLE | SDF_GROUND; |
| s->n_chan = 8 * 7 + 2; |
| s->insn_read = jr3_pci_ai_insn_read; |
| |
| spriv = jr3_pci_alloc_spriv(dev, s); |
| if (!spriv) |
| return -ENOMEM; |
| |
| /* Channel specific range and maxdata */ |
| s->range_table_list = spriv->range_table_list; |
| s->maxdata_list = spriv->maxdata_list; |
| } |
| |
| /* Reset DSP card */ |
| for (i = 0; i < dev->n_subdevices; i++) |
| writel(0, &block[i].reset); |
| |
| ret = comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev, |
| "comedi/jr3pci.idm", |
| jr3_download_firmware, 0); |
| dev_dbg(dev->class_dev, "Firmware load %d\n", ret); |
| if (ret < 0) |
| return ret; |
| /* |
| * TODO: use firmware to load preferred offset tables. Suggested |
| * format: |
| * model serial Fx Fy Fz Mx My Mz\n |
| * |
| * comedi_load_firmware(dev, &comedi_to_pci_dev(dev)->dev, |
| * "comedi/jr3_offsets_table", |
| * jr3_download_firmware, 1); |
| */ |
| |
| /* |
| * It takes a few milliseconds for software to settle as much as we |
| * can read firmware version |
| */ |
| msleep_interruptible(25); |
| jr3_pci_show_copyright(dev); |
| |
| /* Start card timer */ |
| for (i = 0; i < dev->n_subdevices; i++) { |
| s = &dev->subdevices[i]; |
| spriv = s->private; |
| |
| spriv->next_time_min = jiffies + msecs_to_jiffies(500); |
| } |
| |
| devpriv->dev = dev; |
| timer_setup(&devpriv->timer, jr3_pci_poll_dev, 0); |
| devpriv->timer.expires = jiffies + msecs_to_jiffies(1000); |
| add_timer(&devpriv->timer); |
| |
| return 0; |
| } |
| |
| static void jr3_pci_detach(struct comedi_device *dev) |
| { |
| struct jr3_pci_dev_private *devpriv = dev->private; |
| |
| if (devpriv) |
| del_timer_sync(&devpriv->timer); |
| |
| comedi_pci_detach(dev); |
| } |
| |
| static struct comedi_driver jr3_pci_driver = { |
| .driver_name = "jr3_pci", |
| .module = THIS_MODULE, |
| .auto_attach = jr3_pci_auto_attach, |
| .detach = jr3_pci_detach, |
| }; |
| |
| static int jr3_pci_pci_probe(struct pci_dev *dev, |
| const struct pci_device_id *id) |
| { |
| return comedi_pci_auto_config(dev, &jr3_pci_driver, id->driver_data); |
| } |
| |
| static const struct pci_device_id jr3_pci_pci_table[] = { |
| { PCI_VDEVICE(JR3, 0x1111), BOARD_JR3_1 }, |
| { PCI_VDEVICE(JR3, 0x3111), BOARD_JR3_1 }, |
| { PCI_VDEVICE(JR3, 0x3112), BOARD_JR3_2 }, |
| { PCI_VDEVICE(JR3, 0x3113), BOARD_JR3_3 }, |
| { PCI_VDEVICE(JR3, 0x3114), BOARD_JR3_4 }, |
| { 0 } |
| }; |
| MODULE_DEVICE_TABLE(pci, jr3_pci_pci_table); |
| |
| static struct pci_driver jr3_pci_pci_driver = { |
| .name = "jr3_pci", |
| .id_table = jr3_pci_pci_table, |
| .probe = jr3_pci_pci_probe, |
| .remove = comedi_pci_auto_unconfig, |
| }; |
| module_comedi_pci_driver(jr3_pci_driver, jr3_pci_pci_driver); |
| |
| MODULE_AUTHOR("Comedi https://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi driver for JR3/PCI force sensor board"); |
| MODULE_LICENSE("GPL"); |
| MODULE_FIRMWARE("comedi/jr3pci.idm"); |