/* * Analogy driver for Sensoray Model 526 board * * Copyright (C) 2009 Simon Boulay * * Derived from comedi: * Copyright (C) 2000 David A. Schleef * 2006 Everett Wang * 2009 Ian Abbott * * This code 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 code 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 Xenomai; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* * Original code comes from comedi linux-next staging driver (2009.12.20) * Board documentation: http://www.sensoray.com/products/526data.htm * Everything should work as in comedi: * - Encoder works * - Analog input works * - Analog output works * - PWM output works * - Commands are not supported yet. */ #include #include #include #include #include /* Board description */ #define S526_GPCT_CHANS 4 #define S526_GPCT_BITS 24 #define S526_AI_CHANS 10 /* 8 regular differential inputs * channel 8 is "reference 0" (+10V) * channel 9 is "reference 1" (0V) */ #define S526_AI_BITS 16 #define S526_AI_TIMEOUT 100 #define S526_AO_CHANS 4 #define S526_AO_BITS 16 #define S526_DIO_CHANS 8 #define S526_DIO_BITS 1 /* Ports */ #define S526_IOSIZE 0x40 /* 64 bytes */ #define S526_DEFAULT_ADDRESS 0x2C0 /* Manufacturing default */ /* Registers */ #define REG_TCR 0x00 #define REG_WDC 0x02 #define REG_DAC 0x04 #define REG_ADC 0x06 #define REG_ADD 0x08 #define REG_DIO 0x0A #define REG_IER 0x0C #define REG_ISR 0x0E #define REG_MSC 0x10 #define REG_C0L 0x12 #define REG_C0H 0x14 #define REG_C0M 0x16 #define REG_C0C 0x18 #define REG_C1L 0x1A #define REG_C1H 0x1C #define REG_C1M 0x1E #define REG_C1C 0x20 #define REG_C2L 0x22 #define REG_C2H 0x24 #define REG_C2M 0x26 #define REG_C2C 0x28 #define REG_C3L 0x2A #define REG_C3H 0x2C #define REG_C3M 0x2E #define REG_C3C 0x30 #define REG_EED 0x32 #define REG_EEC 0x34 #define ISR_ADC_DONE 0x4 struct counter_mode_register_t { #if defined (__LITTLE_ENDIAN_BITFIELD) unsigned short coutSource:1; unsigned short coutPolarity:1; unsigned short autoLoadResetRcap:3; unsigned short hwCtEnableSource:2; unsigned short ctEnableCtrl:2; unsigned short clockSource:2; unsigned short countDir:1; unsigned short countDirCtrl:1; unsigned short outputRegLatchCtrl:1; unsigned short preloadRegSel:1; unsigned short reserved:1; #elif defined(__BIG_ENDIAN_BITFIELD) unsigned short reserved:1; unsigned short preloadRegSel:1; unsigned short outputRegLatchCtrl:1; unsigned short countDirCtrl:1; unsigned short countDir:1; unsigned short clockSource:2; unsigned short ctEnableCtrl:2; unsigned short hwCtEnableSource:2; unsigned short autoLoadResetRcap:3; unsigned short coutPolarity:1; unsigned short coutSource:1; #else #error Unknown bit field order #endif }; union cmReg { struct counter_mode_register_t reg; unsigned short value; }; /* Application Classes for GPCT Subdevices */ enum S526_GPCT_APP_CLASS { CountingAndTimeMeasurement, SinglePulseGeneration, PulseTrainGeneration, PositionMeasurement, Miscellaneous }; /* GPCT subdevices configuration */ #define MAX_GPCT_CONFIG_DATA 6 struct s526GPCTConfig { enum S526_GPCT_APP_CLASS app; int data[MAX_GPCT_CONFIG_DATA]; }; typedef struct s526_priv { unsigned long io_base; } s526_priv_t; struct s526_subd_gpct_priv { struct s526GPCTConfig config[4]; }; struct s526_subd_ai_priv { uint16_t config; }; struct s526_subd_ao_priv { uint16_t readback[2]; }; struct s526_subd_dio_priv { int io_bits; unsigned int state; }; #define devpriv ((s526_priv_t*)(dev->priv)) #define ADDR_REG(reg) (devpriv->io_base + (reg)) #define ADDR_CHAN_REG(reg, chan) (devpriv->io_base + (reg) + (chan) * 8) static int s526_gpct_insn_config(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_gpct_priv *subdpriv = (struct s526_subd_gpct_priv *)subd->priv; unsigned int *data = (unsigned int *)insn->data; int subdev_channel = CR_CHAN(insn->chan_desc); int i; short value; union cmReg cmReg; a4l_dbg(1, drv_dbg, dev, "s526_gpct_insn_config: Configuring Channel %d\n", subdev_channel); for (i = 0; i < MAX_GPCT_CONFIG_DATA; i++) { subdpriv->config[subdev_channel].data[i] = data[i]; a4l_dbg(1, drv_dbg, dev, "data[%d]=%x\n", i, data[i]); } switch (data[0]) { case A4L_INSN_CONFIG_GPCT_QUADRATURE_ENCODER: /* * data[0]: Application Type * data[1]: Counter Mode Register Value * data[2]: Pre-load Register Value * data[3]: Conter Control Register */ a4l_dbg(1, drv_dbg, dev, "s526_gpct_insn_config: Configuring Encoder\n"); subdpriv->config[subdev_channel].app = PositionMeasurement; /* Set Counter Mode Register */ cmReg.value = data[1] & 0xFFFF; a4l_dbg(1, drv_dbg, dev, "Counter Mode register=%x\n", cmReg.value); outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); /* Reset the counter if it is software preload */ if (cmReg.reg.autoLoadResetRcap == 0) { outw(0x8000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); /* Reset the counter */ /* outw(0x4000, ADDR_CHAN_REG(REG_C0C, subdev_channel)); /\* Load the counter from PR0 *\/ */ } break; case A4L_INSN_CONFIG_GPCT_SINGLE_PULSE_GENERATOR: /* * data[0]: Application Type * data[1]: Counter Mode Register Value * data[2]: Pre-load Register 0 Value * data[3]: Pre-load Register 1 Value * data[4]: Conter Control Register */ a4l_dbg(1, drv_dbg, dev, "s526_gpct_insn_config: Configuring SPG\n"); subdpriv->config[subdev_channel].app = SinglePulseGeneration; /* Set Counter Mode Register */ cmReg.value = (short)(data[1] & 0xFFFF); cmReg.reg.preloadRegSel = 0; /* PR0 */ outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); /* Load the pre-load register 0 high word */ value = (short)((data[2] >> 16) & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); /* Load the pre-load register 0 low word */ value = (short)(data[2] & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); /* Set Counter Mode Register */ cmReg.value = (short)(data[1] & 0xFFFF); cmReg.reg.preloadRegSel = 1; /* PR1 */ outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); /* Load the pre-load register 1 high word */ value = (short)((data[3] >> 16) & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); /* Load the pre-load register 1 low word */ value = (short)(data[3] & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); /* Write the Counter Control Register */ if (data[4] != 0) { value = (short)(data[4] & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); } break; case A4L_INSN_CONFIG_GPCT_PULSE_TRAIN_GENERATOR: /* * data[0]: Application Type * data[1]: Counter Mode Register Value * data[2]: Pre-load Register 0 Value * data[3]: Pre-load Register 1 Value * data[4]: Conter Control Register */ a4l_dbg(1, drv_dbg, dev, "s526_gpct_insn_config: Configuring PTG\n"); subdpriv->config[subdev_channel].app = PulseTrainGeneration; /* Set Counter Mode Register */ cmReg.value = (short)(data[1] & 0xFFFF); cmReg.reg.preloadRegSel = 0; /* PR0 */ outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); /* Load the pre-load register 0 high word */ value = (short)((data[2] >> 16) & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); /* Load the pre-load register 0 low word */ value = (short)(data[2] & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); /* Set Counter Mode Register */ cmReg.value = (short)(data[1] & 0xFFFF); cmReg.reg.preloadRegSel = 1; /* PR1 */ outw(cmReg.value, ADDR_CHAN_REG(REG_C0M, subdev_channel)); /* Load the pre-load register 1 high word */ value = (short)((data[3] >> 16) & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); /* Load the pre-load register 1 low word */ value = (short)(data[3] & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); /* Write the Counter Control Register */ if (data[4] != 0) { value = (short)(data[4] & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0C, subdev_channel)); } break; default: a4l_err(dev, "s526_gpct_insn_config: unsupported GPCT_insn_config\n"); return -EINVAL; break; } return 0; } static int s526_gpct_rinsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; uint32_t *data = (uint32_t *)insn->data; int counter_channel = CR_CHAN(insn->chan_desc); unsigned short datalow; unsigned short datahigh; int i; if (insn->data_size <= 0) { a4l_err(dev, "s526_gpct_rinsn: data size should be > 0\n"); return -EINVAL; } for (i = 0; i < insn->data_size / sizeof(uint32_t); i++) { datalow = inw(ADDR_CHAN_REG(REG_C0L, counter_channel)); datahigh = inw(ADDR_CHAN_REG(REG_C0H, counter_channel)); data[i] = (int)(datahigh & 0x00FF); data[i] = (data[i] << 16) | (datalow & 0xFFFF); a4l_dbg(1, drv_dbg, dev, "s526_gpct_rinsn GPCT[%d]: %x(0x%04x, 0x%04x)\n", counter_channel, data[i], datahigh, datalow); } return 0; } static int s526_gpct_winsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_gpct_priv *subdpriv = (struct s526_subd_gpct_priv *)subd->priv; uint32_t *data = (uint32_t *)insn->data; int subdev_channel = CR_CHAN(insn->chan_desc); short value; union cmReg cmReg; a4l_dbg(1, drv_dbg, dev, "s526_gpct_winsn: GPCT_INSN_WRITE on channel %d\n", subdev_channel); cmReg.value = inw(ADDR_CHAN_REG(REG_C0M, subdev_channel)); a4l_dbg(1, drv_dbg, dev, "s526_gpct_winsn: Counter Mode Register: %x\n", cmReg.value); /* Check what Application of Counter this channel is configured for */ switch (subdpriv->config[subdev_channel].app) { case PositionMeasurement: a4l_dbg(1, drv_dbg, dev, "s526_gpct_winsn: INSN_WRITE: PM\n"); outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H, subdev_channel)); outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel)); break; case SinglePulseGeneration: a4l_dbg(1, drv_dbg, dev, "s526_gpct_winsn: INSN_WRITE: SPG\n"); outw(0xFFFF & ((*data) >> 16), ADDR_CHAN_REG(REG_C0H, subdev_channel)); outw(0xFFFF & (*data), ADDR_CHAN_REG(REG_C0L, subdev_channel)); break; case PulseTrainGeneration: /* * data[0] contains the PULSE_WIDTH * data[1] contains the PULSE_PERIOD * @pre PULSE_PERIOD > PULSE_WIDTH > 0 * The above periods must be expressed as a multiple of the * pulse frequency on the selected source */ a4l_dbg(1, drv_dbg, dev, "s526_gpct_winsn: INSN_WRITE: PTG\n"); if ((data[1] > data[0]) && (data[0] > 0)) { (subdpriv->config[subdev_channel]).data[0] = data[0]; (subdpriv->config[subdev_channel]).data[1] = data[1]; } else { a4l_err(dev, "s526_gpct_winsn: INSN_WRITE: PTG: Problem with Pulse params -> %du %du\n", data[0], data[1]); return -EINVAL; } value = (short)((*data >> 16) & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0H, subdev_channel)); value = (short)(*data & 0xFFFF); outw(value, ADDR_CHAN_REG(REG_C0L, subdev_channel)); break; default: /* Impossible */ a4l_err(dev, "s526_gpct_winsn: INSN_WRITE: Functionality %d not implemented yet\n", subdpriv->config[subdev_channel].app); return -EINVAL; } return 0; } static int s526_ai_insn_config(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_ai_priv *subdpriv = (struct s526_subd_ai_priv *)subd->priv; unsigned int *data = (unsigned int *)insn->data; if (insn->data_size < sizeof(unsigned int)) return -EINVAL; /* data[0] : channels was set in relevant bits. * data[1] : delay */ /* COMMENT: abbotti 2008-07-24: I don't know why you'd want to * enable channels here. The channel should be enabled in the * INSN_READ handler. */ /* Enable ADC interrupt */ outw(ISR_ADC_DONE, ADDR_REG(REG_IER)); a4l_dbg(1, drv_dbg, dev, "s526_ai_insn_config: ADC current value: 0x%04x\n", inw(ADDR_REG(REG_ADC))); subdpriv->config = (data[0] & 0x3FF) << 5; if (data[1] > 0) subdpriv->config |= 0x8000; /* set the delay */ subdpriv->config |= 0x0001; /* ADC start bit. */ return 0; } static int s526_ai_rinsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_ai_priv *subdpriv = (struct s526_subd_ai_priv *)subd->priv; uint16_t *data = (uint16_t *)insn->data; int n, i; int chan = CR_CHAN(insn->chan_desc); uint16_t value; uint16_t d; uint16_t status; /* Set configured delay, enable channel for this channel only, * select "ADC read" channel, set "ADC start" bit. */ value = (subdpriv->config & 0x8000) | ((1 << 5) << chan) | (chan << 1) | 0x0001; /* convert n samples */ for (n = 0; n < insn->data_size / sizeof(uint16_t); n++) { /* trigger conversion */ outw(value, ADDR_REG(REG_ADC)); a4l_dbg(1, drv_dbg, dev, "s526_ai_rinsn: Wrote 0x%04x to ADC\n", value); /* wait for conversion to end */ for (i = 0; i < S526_AI_TIMEOUT; i++) { status = inw(ADDR_REG(REG_ISR)); if (status & ISR_ADC_DONE) { outw(ISR_ADC_DONE, ADDR_REG(REG_ISR)); break; } } if (i == S526_AI_TIMEOUT) { a4l_warn(dev, "s526_ai_rinsn: ADC(0x%04x) timeout\n", inw(ADDR_REG(REG_ISR))); return -ETIMEDOUT; } /* read data */ d = inw(ADDR_REG(REG_ADD)); a4l_dbg(1, drv_dbg, dev, "s526_ai_rinsn: AI[%d]=0x%04x\n", n, (uint16_t)(d & 0xFFFF)); /* munge data */ data[n] = d ^ 0x8000; } return 0; } static int s526_ao_winsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_ao_priv *subdpriv = (struct s526_subd_ao_priv *)subd->priv; uint16_t *data = (uint16_t *)insn->data; int i; int chan = CR_CHAN(insn->chan_desc); uint16_t val; val = chan << 1; outw(val, ADDR_REG(REG_DAC)); for (i = 0; i < insn->data_size / sizeof(uint16_t); i++) { outw(data[i], ADDR_REG(REG_ADD)); /* write the data to preload register */ subdpriv->readback[chan] = data[i]; outw(val + 1, ADDR_REG(REG_DAC)); /* starts the D/A conversion. */ } return 0; } static int s526_ao_rinsn(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct s526_subd_ao_priv *subdpriv = (struct s526_subd_ao_priv *)subd->priv; uint16_t *data = (uint16_t *)insn->data; int i; int chan = CR_CHAN(insn->chan_desc); for (i = 0; i < insn->data_size / sizeof(uint16_t); i++) data[i] = subdpriv->readback[chan]; return 0; } static int s526_dio_insn_config(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_dio_priv *subdpriv = (struct s526_subd_dio_priv *)subd->priv; unsigned int *data = (unsigned int *)insn->data; int chan = CR_CHAN(insn->chan_desc); int group, mask; group = chan >> 2; mask = 0xF << (group << 2); switch (data[0]) { case A4L_INSN_CONFIG_DIO_OUTPUT: subdpriv->state |= 1 << (group + 10); /* bit 10/11 set the * group 1/2's mode */ subdpriv->io_bits |= mask; break; case A4L_INSN_CONFIG_DIO_INPUT: subdpriv->state &= ~(1 << (group + 10)); /* 1 is output, 0 is * input. */ subdpriv->io_bits &= ~mask; break; case A4L_INSN_CONFIG_DIO_QUERY: data[1] = (subdpriv->io_bits & mask) ? A4L_OUTPUT : A4L_INPUT; return 0; default: return -EINVAL; } outw(subdpriv->state, ADDR_REG(REG_DIO)); return 0; } static int s526_dio_insn_bits(struct a4l_subdevice *subd, struct a4l_kernel_instruction *insn) { struct a4l_device *dev = subd->dev; struct s526_subd_dio_priv *subdpriv = (struct s526_subd_dio_priv *)subd->priv; uint8_t *data = (uint8_t *)insn->data; if (insn->data_size != 2 * sizeof(uint8_t)) return -EINVAL; if (data[0]) { subdpriv->state &= ~(data[0]); subdpriv->state |= data[0] & data[1]; outw(subdpriv->state, ADDR_REG(REG_DIO)); } data[1] = inw(ADDR_REG(REG_DIO)) & 0xFF; /* low 8 bits are the data */ return 0; } /* --- Channels descriptor --- */ static struct a4l_channels_desc s526_chan_desc_gpct = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = S526_GPCT_CHANS, .chans = { {A4L_CHAN_AREF_GROUND, S526_GPCT_BITS}, }, }; static struct a4l_channels_desc s526_chan_desc_ai = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = S526_AI_CHANS, .chans = { {A4L_CHAN_AREF_GROUND, S526_AI_BITS}, }, }; static struct a4l_channels_desc s526_chan_desc_ao = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = S526_AO_CHANS, .chans = { {A4L_CHAN_AREF_GROUND, S526_AO_BITS}, }, }; static struct a4l_channels_desc s526_chan_desc_dio = { .mode = A4L_CHAN_GLOBAL_CHANDESC, .length = S526_DIO_CHANS, .chans = { {A4L_CHAN_AREF_GROUND, S526_DIO_BITS}, }, }; /* --- Subdevice initialization functions --- */ /* General purpose counter/timer (gpct) */ static void setup_subd_gpct(struct a4l_subdevice *subd) { subd->flags = A4L_SUBD_COUNTER; subd->chan_desc = &s526_chan_desc_gpct; subd->insn_read = s526_gpct_rinsn; subd->insn_config = s526_gpct_insn_config; subd->insn_write = s526_gpct_winsn; } /* Analog input subdevice */ static void setup_subd_ai(struct a4l_subdevice *subd) { subd->flags = A4L_SUBD_AI; subd->chan_desc = &s526_chan_desc_ai; subd->rng_desc = &a4l_range_bipolar10; subd->insn_read = s526_ai_rinsn; subd->insn_config = s526_ai_insn_config; } /* Analog output subdevice */ static void setup_subd_ao(struct a4l_subdevice *subd) { subd->flags = A4L_SUBD_AO; subd->chan_desc = &s526_chan_desc_ao; subd->rng_desc = &a4l_range_bipolar10; subd->insn_write = s526_ao_winsn; subd->insn_read = s526_ao_rinsn; } /* Digital i/o subdevice */ static void setup_subd_dio(struct a4l_subdevice *subd) { subd->flags = A4L_SUBD_DIO; subd->chan_desc = &s526_chan_desc_dio; subd->rng_desc = &range_digital; subd->insn_bits = s526_dio_insn_bits; subd->insn_config = s526_dio_insn_config; } struct setup_subd { void (*setup_func) (struct a4l_subdevice *); int sizeof_priv; }; static struct setup_subd setup_subds[4] = { { .setup_func = setup_subd_gpct, .sizeof_priv = sizeof(struct s526_subd_gpct_priv), }, { .setup_func = setup_subd_ai, .sizeof_priv = sizeof(struct s526_subd_ai_priv), }, { .setup_func = setup_subd_ao, .sizeof_priv = sizeof(struct s526_subd_ao_priv), }, { .setup_func = setup_subd_dio, .sizeof_priv = sizeof(struct s526_subd_dio_priv), }, }; static int dev_s526_attach(struct a4l_device *dev, a4l_lnkdesc_t *arg) { int io_base; int i; int err = 0; if (arg->opts == NULL || arg->opts_size < sizeof(unsigned long)) { a4l_warn(dev, "dev_s526_attach: no attach options specified; " "using defaults: addr=0x%x\n", S526_DEFAULT_ADDRESS); io_base = S526_DEFAULT_ADDRESS; } else { io_base = ((unsigned long *)arg->opts)[0]; } if (!request_region(io_base, S526_IOSIZE, "s526")) { a4l_err(dev, "dev_s526_attach: I/O port conflict\n"); return -EIO; } /* Allocate the subdevice structures. */ for (i = 0; i < 4; i++) { struct a4l_subdevice *subd = a4l_alloc_subd(setup_subds[i].sizeof_priv, setup_subds[i].setup_func); if (subd == NULL) return -ENOMEM; err = a4l_add_subd(dev, subd); if (err != i) return err; } devpriv->io_base = io_base; a4l_info(dev, " attached (address = 0x%x)\n", io_base); return 0; } static int dev_s526_detach(struct a4l_device *dev) { int err = 0; if (devpriv->io_base != 0) release_region(devpriv->io_base, S526_IOSIZE); return err; } static struct a4l_driver drv_s526 = { .owner = THIS_MODULE, .board_name = "analogy_s526", .driver_name = "s526", .attach = dev_s526_attach, .detach = dev_s526_detach, .privdata_size = sizeof(s526_priv_t), }; static int __init drv_s526_init(void) { return a4l_register_drv(&drv_s526); } static void __exit drv_s526_cleanup(void) { a4l_unregister_drv(&drv_s526); } MODULE_DESCRIPTION("Analogy driver for Sensoray Model 526 board."); MODULE_LICENSE("GPL"); module_init(drv_s526_init); module_exit(drv_s526_cleanup);