| /* |
| 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> |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
| |
| */ |
| /* |
| * 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 http://www.comedi.org in the |
| * comedi_nonfree_firmware tarball. The file is called "jr3pci.idm". |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/pci.h> |
| #include <linux/delay.h> |
| #include <linux/ctype.h> |
| #include <linux/firmware.h> |
| #include <linux/jiffies.h> |
| #include <linux/slab.h> |
| #include <linux/timer.h> |
| |
| #include "../comedidev.h" |
| |
| #include "jr3_pci.h" |
| |
| #define PCI_VENDOR_ID_JR3 0x1762 |
| #define PCI_DEVICE_ID_JR3_1_CHANNEL 0x3111 |
| #define PCI_DEVICE_ID_JR3_1_CHANNEL_NEW 0x1111 |
| #define PCI_DEVICE_ID_JR3_2_CHANNEL 0x3112 |
| #define PCI_DEVICE_ID_JR3_3_CHANNEL 0x3113 |
| #define PCI_DEVICE_ID_JR3_4_CHANNEL 0x3114 |
| |
| struct jr3_pci_dev_private { |
| struct jr3_t __iomem *iobase; |
| int n_channels; |
| struct timer_list timer; |
| }; |
| |
| struct poll_delay_t { |
| int min; |
| int max; |
| }; |
| |
| struct jr3_pci_subdev_private { |
| struct jr3_channel __iomem *channel; |
| unsigned long next_time_min; |
| unsigned long next_time_max; |
| enum { 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 |
| } state; |
| int channel_no; |
| int serial_no; |
| int model_no; |
| struct { |
| int length; |
| struct comedi_krange range; |
| } range[9]; |
| const struct comedi_lrange *range_table_list[8 * 7 + 2]; |
| unsigned int maxdata_list[8 * 7 + 2]; |
| u16 errors; |
| int retries; |
| }; |
| |
| /* Hotplug firmware loading stuff */ |
| static int comedi_load_firmware(struct comedi_device *dev, const char *name, |
| int (*cb)(struct comedi_device *dev, |
| const u8 *data, size_t size)) |
| { |
| struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
| int result = 0; |
| const struct firmware *fw; |
| char *firmware_path; |
| static const char *prefix = "comedi/"; |
| |
| firmware_path = kmalloc(strlen(prefix) + strlen(name) + 1, GFP_KERNEL); |
| if (!firmware_path) { |
| result = -ENOMEM; |
| } else { |
| firmware_path[0] = '\0'; |
| strcat(firmware_path, prefix); |
| strcat(firmware_path, name); |
| result = request_firmware(&fw, firmware_path, &pcidev->dev); |
| if (result == 0) { |
| if (!cb) |
| result = -EINVAL; |
| else |
| result = cb(dev, fw->data, fw->size); |
| release_firmware(fw); |
| } |
| kfree(firmware_path); |
| } |
| return result; |
| } |
| |
| static struct poll_delay_t poll_delay_min_max(int min, int max) |
| { |
| struct poll_delay_t result; |
| |
| result.min = min; |
| result.max = max; |
| return result; |
| } |
| |
| static int is_complete(struct jr3_channel __iomem *channel) |
| { |
| return get_s16(&channel->command_word0) == 0; |
| } |
| |
| struct transform_t { |
| struct { |
| u16 link_type; |
| s16 link_amount; |
| } link[8]; |
| }; |
| |
| static void set_transforms(struct jr3_channel __iomem *channel, |
| struct transform_t transf, short num) |
| { |
| int i; |
| |
| num &= 0x000f; /* Make sure that 0 <= num <= 15 */ |
| for (i = 0; i < 8; i++) { |
| set_u16(&channel->transforms[num].link[i].link_type, |
| transf.link[i].link_type); |
| udelay(1); |
| set_s16(&channel->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_channel __iomem *channel, |
| short transf_num) |
| { |
| set_s16(&channel->command_word0, 0x0500 + (transf_num & 0x000f)); |
| } |
| |
| static void use_offset(struct jr3_channel __iomem *channel, short offset_num) |
| { |
| set_s16(&channel->command_word0, 0x0600 + (offset_num & 0x000f)); |
| } |
| |
| static void set_offset(struct jr3_channel __iomem *channel) |
| { |
| set_s16(&channel->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_channel __iomem *channel, |
| struct six_axis_t full_scale) |
| { |
| set_s16(&channel->full_scale.fx, full_scale.fx); |
| set_s16(&channel->full_scale.fy, full_scale.fy); |
| set_s16(&channel->full_scale.fz, full_scale.fz); |
| set_s16(&channel->full_scale.mx, full_scale.mx); |
| set_s16(&channel->full_scale.my, full_scale.my); |
| set_s16(&channel->full_scale.mz, full_scale.mz); |
| set_s16(&channel->command_word0, 0x0a00); |
| } |
| |
| static struct six_axis_t get_min_full_scales(struct jr3_channel __iomem |
| *channel) |
| { |
| struct six_axis_t result; |
| result.fx = get_s16(&channel->min_full_scale.fx); |
| result.fy = get_s16(&channel->min_full_scale.fy); |
| result.fz = get_s16(&channel->min_full_scale.fz); |
| result.mx = get_s16(&channel->min_full_scale.mx); |
| result.my = get_s16(&channel->min_full_scale.my); |
| result.mz = get_s16(&channel->min_full_scale.mz); |
| return result; |
| } |
| |
| static struct six_axis_t get_max_full_scales(struct jr3_channel __iomem |
| *channel) |
| { |
| struct six_axis_t result; |
| result.fx = get_s16(&channel->max_full_scale.fx); |
| result.fy = get_s16(&channel->max_full_scale.fy); |
| result.fz = get_s16(&channel->max_full_scale.fz); |
| result.mx = get_s16(&channel->max_full_scale.mx); |
| result.my = get_s16(&channel->max_full_scale.my); |
| result.mz = get_s16(&channel->max_full_scale.mz); |
| return result; |
| } |
| |
| static int jr3_pci_ai_insn_read(struct comedi_device *dev, |
| struct comedi_subdevice *s, |
| struct comedi_insn *insn, unsigned int *data) |
| { |
| int result; |
| struct jr3_pci_subdev_private *p; |
| int channel; |
| |
| p = s->private; |
| channel = CR_CHAN(insn->chanspec); |
| if (p == NULL || channel > 57) { |
| result = -EINVAL; |
| } else { |
| int i; |
| |
| result = insn->n; |
| if (p->state != state_jr3_done || |
| (get_u16(&p->channel->errors) & (watch_dog | watch_dog2 | |
| sensor_change))) { |
| /* No sensor or sensor changed */ |
| if (p->state == state_jr3_done) { |
| /* Restart polling */ |
| p->state = state_jr3_poll; |
| } |
| result = -EAGAIN; |
| } |
| for (i = 0; i < insn->n; i++) { |
| if (channel < 56) { |
| int axis, filter; |
| |
| axis = channel % 8; |
| filter = channel / 8; |
| if (p->state != state_jr3_done) { |
| data[i] = 0; |
| } else { |
| int F = 0; |
| switch (axis) { |
| case 0: |
| F = get_s16(&p->channel-> |
| filter[filter].fx); |
| break; |
| case 1: |
| F = get_s16(&p->channel-> |
| filter[filter].fy); |
| break; |
| case 2: |
| F = get_s16(&p->channel-> |
| filter[filter].fz); |
| break; |
| case 3: |
| F = get_s16(&p->channel-> |
| filter[filter].mx); |
| break; |
| case 4: |
| F = get_s16(&p->channel-> |
| filter[filter].my); |
| break; |
| case 5: |
| F = get_s16(&p->channel-> |
| filter[filter].mz); |
| break; |
| case 6: |
| F = get_s16(&p->channel-> |
| filter[filter].v1); |
| break; |
| case 7: |
| F = get_s16(&p->channel-> |
| filter[filter].v2); |
| break; |
| } |
| data[i] = F + 0x4000; |
| } |
| } else if (channel == 56) { |
| if (p->state != state_jr3_done) |
| data[i] = 0; |
| else |
| data[i] = |
| get_u16(&p->channel->model_no); |
| } else if (channel == 57) { |
| if (p->state != state_jr3_done) |
| data[i] = 0; |
| else |
| data[i] = |
| get_u16(&p->channel->serial_no); |
| } |
| } |
| } |
| return result; |
| } |
| |
| static int jr3_pci_open(struct comedi_device *dev) |
| { |
| int i; |
| struct jr3_pci_dev_private *devpriv = dev->private; |
| |
| dev_dbg(dev->class_dev, "jr3_pci_open\n"); |
| for (i = 0; i < devpriv->n_channels; i++) { |
| struct jr3_pci_subdev_private *p; |
| |
| p = dev->subdevices[i].private; |
| if (p) { |
| dev_dbg(dev->class_dev, "serial: %p %d (%d)\n", p, |
| p->serial_no, p->channel_no); |
| } |
| } |
| return 0; |
| } |
| |
| static int read_idm_word(const u8 *data, size_t size, int *pos, |
| unsigned int *val) |
| { |
| int result = 0; |
| if (pos && val) { |
| /* Skip over non hex */ |
| for (; *pos < size && !isxdigit(data[*pos]); (*pos)++) |
| ; |
| /* Collect value */ |
| *val = 0; |
| for (; *pos < size; (*pos)++) { |
| int value; |
| value = hex_to_bin(data[*pos]); |
| if (value >= 0) { |
| result = 1; |
| *val = (*val << 4) + value; |
| } else { |
| break; |
| } |
| } |
| } |
| return result; |
| } |
| |
| static int jr3_download_firmware(struct comedi_device *dev, const u8 *data, |
| size_t size) |
| { |
| /* |
| * IDM file format is: |
| * { count, address, data <count> } * |
| * ffff |
| */ |
| int result, more, pos, OK; |
| |
| result = 0; |
| more = 1; |
| pos = 0; |
| OK = 0; |
| while (more) { |
| unsigned int count, addr; |
| |
| more = more && read_idm_word(data, size, &pos, &count); |
| if (more && count == 0xffff) { |
| OK = 1; |
| break; |
| } |
| more = more && read_idm_word(data, size, &pos, &addr); |
| while (more && count > 0) { |
| unsigned int dummy; |
| more = more && read_idm_word(data, size, &pos, &dummy); |
| count--; |
| } |
| } |
| |
| if (!OK) { |
| result = -ENODATA; |
| } else { |
| int i; |
| struct jr3_pci_dev_private *p = dev->private; |
| |
| for (i = 0; i < p->n_channels; i++) { |
| struct jr3_pci_subdev_private *sp; |
| |
| sp = dev->subdevices[i].private; |
| more = 1; |
| pos = 0; |
| while (more) { |
| unsigned int count, addr; |
| more = more && |
| read_idm_word(data, size, &pos, &count); |
| if (more && count == 0xffff) |
| break; |
| more = more && |
| read_idm_word(data, size, &pos, &addr); |
| dev_dbg(dev->class_dev, |
| "Loading#%d %4.4x bytes at %4.4x\n", |
| i, count, addr); |
| while (more && count > 0) { |
| if (addr & 0x4000) { |
| /* 16 bit data, never seen |
| * in real life!! */ |
| unsigned int data1; |
| |
| more = more && |
| read_idm_word(data, |
| size, &pos, |
| &data1); |
| count--; |
| /* jr3[addr + 0x20000 * pnum] = |
| data1; */ |
| } else { |
| /* Download 24 bit program */ |
| unsigned int data1, data2; |
| |
| more = more && |
| read_idm_word(data, |
| size, &pos, |
| &data1); |
| more = more && |
| read_idm_word(data, size, |
| &pos, |
| &data2); |
| count -= 2; |
| if (more) { |
| set_u16(&p-> |
| iobase->channel |
| [i].program_low |
| [addr], data1); |
| udelay(1); |
| set_u16(&p-> |
| iobase->channel |
| [i].program_high |
| [addr], data2); |
| udelay(1); |
| } |
| } |
| addr++; |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| static struct poll_delay_t jr3_pci_poll_subdevice(struct comedi_subdevice *s) |
| { |
| struct poll_delay_t result = poll_delay_min_max(1000, 2000); |
| struct jr3_pci_subdev_private *p = s->private; |
| int i; |
| |
| if (p) { |
| struct jr3_channel __iomem *channel = p->channel; |
| int errors = get_u16(&channel->errors); |
| |
| if (errors != p->errors) |
| p->errors = errors; |
| |
| if (errors & (watch_dog | watch_dog2 | sensor_change)) |
| /* Sensor communication lost, force poll mode */ |
| p->state = state_jr3_poll; |
| |
| switch (p->state) { |
| case state_jr3_poll: { |
| u16 model_no = get_u16(&channel->model_no); |
| u16 serial_no = get_u16(&channel->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. |
| */ |
| result = poll_delay_min_max(1000, 2000); |
| } else { |
| p->retries = 0; |
| p->state = |
| state_jr3_init_wait_for_offset; |
| result = poll_delay_min_max(1000, 2000); |
| } |
| } |
| break; |
| case state_jr3_init_wait_for_offset: |
| p->retries++; |
| if (p->retries < 10) { |
| /* Wait for offeset to stabilize |
| * (< 10 s according to manual) */ |
| result = poll_delay_min_max(1000, 2000); |
| } else { |
| struct transform_t transf; |
| |
| p->model_no = get_u16(&channel->model_no); |
| p->serial_no = get_u16(&channel->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(channel, transf, 0); |
| use_transform(channel, 0); |
| p->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(channel)) { |
| result = poll_delay_min_max(20, 100); |
| } else { |
| /* Set full scale */ |
| struct six_axis_t min_full_scale; |
| struct six_axis_t max_full_scale; |
| |
| min_full_scale = get_min_full_scales(channel); |
| max_full_scale = get_max_full_scales(channel); |
| set_full_scales(channel, max_full_scale); |
| |
| p->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(channel)) { |
| result = poll_delay_min_max(20, 100); |
| } else { |
| struct force_array __iomem *full_scale; |
| |
| /* Use ranges in kN or we will |
| * overflow around 2000N! */ |
| full_scale = &channel->full_scale; |
| p->range[0].range.min = |
| -get_s16(&full_scale->fx) * 1000; |
| p->range[0].range.max = |
| get_s16(&full_scale->fx) * 1000; |
| p->range[1].range.min = |
| -get_s16(&full_scale->fy) * 1000; |
| p->range[1].range.max = |
| get_s16(&full_scale->fy) * 1000; |
| p->range[2].range.min = |
| -get_s16(&full_scale->fz) * 1000; |
| p->range[2].range.max = |
| get_s16(&full_scale->fz) * 1000; |
| p->range[3].range.min = |
| -get_s16(&full_scale->mx) * 100; |
| p->range[3].range.max = |
| get_s16(&full_scale->mx) * 100; |
| p->range[4].range.min = |
| -get_s16(&full_scale->my) * 100; |
| p->range[4].range.max = |
| get_s16(&full_scale->my) * 100; |
| p->range[5].range.min = |
| -get_s16(&full_scale->mz) * 100; |
| p->range[5].range.max = |
| get_s16(&full_scale->mz) * 100; /* ?? */ |
| p->range[6].range.min = |
| -get_s16(&full_scale->v1) * 100;/* ?? */ |
| p->range[6].range.max = |
| get_s16(&full_scale->v1) * 100; /* ?? */ |
| p->range[7].range.min = |
| -get_s16(&full_scale->v2) * 100;/* ?? */ |
| p->range[7].range.max = |
| get_s16(&full_scale->v2) * 100; /* ?? */ |
| p->range[8].range.min = 0; |
| p->range[8].range.max = 65535; |
| |
| use_offset(channel, 0); |
| p->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(channel)) { |
| result = poll_delay_min_max(20, 100); |
| } else { |
| set_s16(&channel->offsets.fx, 0); |
| set_s16(&channel->offsets.fy, 0); |
| set_s16(&channel->offsets.fz, 0); |
| set_s16(&channel->offsets.mx, 0); |
| set_s16(&channel->offsets.my, 0); |
| set_s16(&channel->offsets.mz, 0); |
| |
| set_offset(channel); |
| |
| p->state = state_jr3_done; |
| } |
| break; |
| case state_jr3_done: |
| poll_delay_min_max(10000, 20000); |
| break; |
| default: |
| poll_delay_min_max(1000, 2000); |
| break; |
| } |
| } |
| return result; |
| } |
| |
| static void jr3_pci_poll_dev(unsigned long data) |
| { |
| unsigned long flags; |
| struct comedi_device *dev = (struct comedi_device *)data; |
| struct jr3_pci_dev_private *devpriv = dev->private; |
| unsigned long now; |
| int delay; |
| int i; |
| |
| spin_lock_irqsave(&dev->spinlock, flags); |
| delay = 1000; |
| now = jiffies; |
| /* Poll all channels that are ready to be polled */ |
| for (i = 0; i < devpriv->n_channels; i++) { |
| struct jr3_pci_subdev_private *subdevpriv = |
| dev->subdevices[i].private; |
| if (now > subdevpriv->next_time_min) { |
| struct poll_delay_t sub_delay; |
| |
| sub_delay = jr3_pci_poll_subdevice(&dev->subdevices[i]); |
| subdevpriv->next_time_min = |
| jiffies + msecs_to_jiffies(sub_delay.min); |
| subdevpriv->next_time_max = |
| jiffies + msecs_to_jiffies(sub_delay.max); |
| if (sub_delay.max && sub_delay.max < delay) |
| /* |
| * Wake up as late as possible -> |
| * poll as many channels 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 int jr3_pci_auto_attach(struct comedi_device *dev, |
| unsigned long context_unused) |
| { |
| int result; |
| struct pci_dev *pcidev = comedi_to_pci_dev(dev); |
| int i; |
| struct jr3_pci_dev_private *devpriv; |
| |
| if (sizeof(struct jr3_channel) != 0xc00) { |
| dev_err(dev->class_dev, |
| "sizeof(struct jr3_channel) = %x [expected %x]\n", |
| (unsigned)sizeof(struct jr3_channel), 0xc00); |
| return -EINVAL; |
| } |
| |
| devpriv = kzalloc(sizeof(*devpriv), GFP_KERNEL); |
| if (!devpriv) |
| return -ENOMEM; |
| dev->private = devpriv; |
| |
| init_timer(&devpriv->timer); |
| switch (pcidev->device) { |
| case PCI_DEVICE_ID_JR3_1_CHANNEL: |
| case PCI_DEVICE_ID_JR3_1_CHANNEL_NEW: |
| devpriv->n_channels = 1; |
| break; |
| case PCI_DEVICE_ID_JR3_2_CHANNEL: |
| devpriv->n_channels = 2; |
| break; |
| case PCI_DEVICE_ID_JR3_3_CHANNEL: |
| devpriv->n_channels = 3; |
| break; |
| case PCI_DEVICE_ID_JR3_4_CHANNEL: |
| devpriv->n_channels = 4; |
| break; |
| default: |
| dev_err(dev->class_dev, "jr3_pci: pci %s not supported\n", |
| pci_name(pcidev)); |
| return -EINVAL; |
| break; |
| } |
| |
| result = comedi_pci_enable(dev); |
| if (result) |
| return result; |
| |
| devpriv->iobase = pci_ioremap_bar(pcidev, 0); |
| if (!devpriv->iobase) |
| return -ENOMEM; |
| |
| result = comedi_alloc_subdevices(dev, devpriv->n_channels); |
| if (result) |
| return result; |
| |
| dev->open = jr3_pci_open; |
| for (i = 0; i < devpriv->n_channels; i++) { |
| dev->subdevices[i].type = COMEDI_SUBD_AI; |
| dev->subdevices[i].subdev_flags = SDF_READABLE | SDF_GROUND; |
| dev->subdevices[i].n_chan = 8 * 7 + 2; |
| dev->subdevices[i].insn_read = jr3_pci_ai_insn_read; |
| dev->subdevices[i].private = |
| kzalloc(sizeof(struct jr3_pci_subdev_private), |
| GFP_KERNEL); |
| if (dev->subdevices[i].private) { |
| struct jr3_pci_subdev_private *p; |
| int j; |
| |
| p = dev->subdevices[i].private; |
| p->channel = &devpriv->iobase->channel[i].data; |
| dev_dbg(dev->class_dev, "p->channel %p %p (%tx)\n", |
| p->channel, devpriv->iobase, |
| ((char __iomem *)p->channel - |
| (char __iomem *)devpriv->iobase)); |
| p->channel_no = i; |
| for (j = 0; j < 8; j++) { |
| int k; |
| |
| p->range[j].length = 1; |
| p->range[j].range.min = -1000000; |
| p->range[j].range.max = 1000000; |
| for (k = 0; k < 7; k++) { |
| p->range_table_list[j + k * 8] = |
| (struct comedi_lrange *)&p-> |
| range[j]; |
| p->maxdata_list[j + k * 8] = 0x7fff; |
| } |
| } |
| p->range[8].length = 1; |
| p->range[8].range.min = 0; |
| p->range[8].range.max = 65536; |
| |
| p->range_table_list[56] = |
| (struct comedi_lrange *)&p->range[8]; |
| p->range_table_list[57] = |
| (struct comedi_lrange *)&p->range[8]; |
| p->maxdata_list[56] = 0xffff; |
| p->maxdata_list[57] = 0xffff; |
| /* Channel specific range and maxdata */ |
| dev->subdevices[i].range_table = NULL; |
| dev->subdevices[i].range_table_list = |
| p->range_table_list; |
| dev->subdevices[i].maxdata = 0; |
| dev->subdevices[i].maxdata_list = p->maxdata_list; |
| } |
| } |
| |
| /* Reset DSP card */ |
| writel(0, &devpriv->iobase->channel[0].reset); |
| |
| result = comedi_load_firmware(dev, "jr3pci.idm", jr3_download_firmware); |
| dev_dbg(dev->class_dev, "Firmare load %d\n", result); |
| |
| if (result < 0) |
| return result; |
| /* |
| * TODO: use firmware to load preferred offset tables. Suggested |
| * format: |
| * model serial Fx Fy Fz Mx My Mz\n |
| * |
| * comedi_load_firmware(dev, "jr3_offsets_table", |
| * jr3_download_firmware); |
| */ |
| |
| /* |
| * It takes a few milliseconds for software to settle as much as we |
| * can read firmware version |
| */ |
| msleep_interruptible(25); |
| for (i = 0; i < 0x18; i++) { |
| dev_dbg(dev->class_dev, "%c\n", |
| get_u16(&devpriv->iobase->channel[0]. |
| data.copyright[i]) >> 8); |
| } |
| |
| /* Start card timer */ |
| for (i = 0; i < devpriv->n_channels; i++) { |
| struct jr3_pci_subdev_private *p = dev->subdevices[i].private; |
| |
| p->next_time_min = jiffies + msecs_to_jiffies(500); |
| p->next_time_max = jiffies + msecs_to_jiffies(2000); |
| } |
| |
| devpriv->timer.data = (unsigned long)dev; |
| devpriv->timer.function = jr3_pci_poll_dev; |
| devpriv->timer.expires = jiffies + msecs_to_jiffies(1000); |
| add_timer(&devpriv->timer); |
| |
| return result; |
| } |
| |
| static void jr3_pci_detach(struct comedi_device *dev) |
| { |
| int i; |
| struct jr3_pci_dev_private *devpriv = dev->private; |
| |
| if (devpriv) { |
| del_timer_sync(&devpriv->timer); |
| |
| if (dev->subdevices) { |
| for (i = 0; i < devpriv->n_channels; i++) |
| kfree(dev->subdevices[i].private); |
| } |
| if (devpriv->iobase) |
| iounmap(devpriv->iobase); |
| } |
| comedi_pci_disable(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 DEFINE_PCI_DEVICE_TABLE(jr3_pci_pci_table) = { |
| { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_1_CHANNEL) }, |
| { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_1_CHANNEL_NEW) }, |
| { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_2_CHANNEL) }, |
| { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_3_CHANNEL) }, |
| { PCI_DEVICE(PCI_VENDOR_ID_JR3, PCI_DEVICE_ID_JR3_4_CHANNEL) }, |
| { 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 http://www.comedi.org"); |
| MODULE_DESCRIPTION("Comedi low-level driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_FIRMWARE("comedi/jr3pci.idm"); |