/* * GRPWM PWM Driver interface. * * COPYRIGHT (c) 2009. * Cobham Gaisler AB, * * The license and distribution terms for this file may be * found in the file LICENSE in this distribution or at * http://www.rtems.com/license/LICENSE. */ #include #include #include #include #include #include #include #include #include #include #include /* #define DEBUG 1 */ #ifdef DEBUG #define DBG(x...) printk(x) #define STATIC #else #define DBG(x...) #define STATIC static #endif /*** REGISTER LAYOUT ***/ /* PWM Channel specific registers */ struct grpwm_pwm_regs { volatile unsigned int period; /* 0x00 */ volatile unsigned int comp; /* 0x04 */ volatile unsigned int dbcomp; /* 0x08 */ volatile unsigned int ctrl; /* 0x0C */ }; /* Core common registers */ struct grpwm_regs { volatile unsigned int ctrl; /* 0x00 */ volatile unsigned int scaler; /* 0x04 */ volatile unsigned int ipend; /* 0x08 */ volatile unsigned int cap1; /* 0x0C */ volatile unsigned int cap2; /* 0x10 */ volatile unsigned int wctrl; /* 0x14 */ int reserved0[2]; struct grpwm_pwm_regs pwms[8]; /* 0x20 */ int reserved1[(0x8000-0xA0)/4]; /* 0xA0-0x7FFC */ volatile unsigned int wram[0x8000/4]; /* 0x8000-0xFFFC */ }; /*** REGISTER BIT LAYOUT ***/ /* CTRL REGISTER - 0x0 */ #define GRPWM_CTRL_EN_BIT 0 #define GRPWM_CTRL_SCSEL_BIT 8 #define GRPWM_CTRL_NOUP_BIT 12 #define GRPWM_CTRL_EN (1<minor_drv, dev->parent->dev->name); priv = dev->priv = malloc(sizeof(struct grpwm_priv)); if ( !priv ) return DRVMGR_NOMEM; memset(priv, 0, sizeof(*priv)); priv->dev = dev; /* This core will not find other cores, so we wait for init2() */ return DRVMGR_OK; } int grpwm_init3(struct drvmgr_dev *dev) { struct grpwm_priv *priv = dev->priv; char prefix[32]; rtems_status_code status; if ( !priv ) return DRVMGR_FAIL; if ( grpwm_driver_io_registered == 0) { /* Register the I/O driver only once for all cores */ if ( grpwm_register_io(&grpwm_driver_io_major) ) { /* Failed to register I/O driver */ dev->priv = NULL; return DRVMGR_FAIL; } grpwm_driver_io_registered = 1; } /* I/O system registered and initialized * Now we take care of device initialization. */ if ( grpwm_device_init(priv) ) { free(dev->priv); dev->priv = NULL; return DRVMGR_FAIL; } /* Get Filesystem name prefix */ prefix[0] = '\0'; if ( drvmgr_get_dev_prefix(dev, prefix) ) { /* Failed to get prefix, make sure of a unique FS name * by using the driver minor. */ sprintf(priv->devName, "/dev/grpwm%d", dev->minor_drv); } else { /* Got special prefix, this means we have a bus prefix * And we should use our "bus minor" */ sprintf(priv->devName, "/dev/%sgrpwm%d", prefix, dev->minor_bus); } /* Register Device */ status = rtems_io_register_name(priv->devName, grpwm_driver_io_major, dev->minor_drv); if (status != RTEMS_SUCCESSFUL) { return DRVMGR_FAIL; } return DRVMGR_OK; } /******************* Driver Implementation ***********************/ static rtems_device_driver grpwm_initialize(rtems_device_major_number major, rtems_device_minor_number minor, void *arg); static rtems_device_driver grpwm_open(rtems_device_major_number major, rtems_device_minor_number minor, void *arg); static rtems_device_driver grpwm_close(rtems_device_major_number major, rtems_device_minor_number minor, void *arg); static rtems_device_driver grpwm_read(rtems_device_major_number major, rtems_device_minor_number minor, void *arg); static rtems_device_driver grpwm_write(rtems_device_major_number major, rtems_device_minor_number minor, void *arg); static rtems_device_driver grpwm_ioctl(rtems_device_major_number major, rtems_device_minor_number minor, void *arg); #define GRPWM_DRIVER_TABLE_ENTRY { grpwm_initialize, grpwm_open, grpwm_close, grpwm_read, grpwm_write, grpwm_ioctl } static rtems_driver_address_table grpwm_driver = GRPWM_DRIVER_TABLE_ENTRY; int grpwm_register_io(rtems_device_major_number *m) { rtems_status_code r; if ((r = rtems_io_register_driver(0, &grpwm_driver, m)) == RTEMS_SUCCESSFUL) { DBG("GRPWM driver successfully registered, major: %d\n", *m); } else { switch(r) { case RTEMS_TOO_MANY: DBG("GRPWM rtems_io_register_driver failed: RTEMS_TOO_MANY\n"); return -1; case RTEMS_INVALID_NUMBER: DBG("GRPWM rtems_io_register_driver failed: RTEMS_INVALID_NUMBER\n"); return -1; case RTEMS_RESOURCE_IN_USE: DBG("GRPWM rtems_io_register_driver failed: RTEMS_RESOURCE_IN_USE\n"); return -1; default: DBG("GRPWM rtems_io_register_driver failed\n"); return -1; } } return 0; } static void grpwm_scaler_set( struct grpwm_regs *regs, int scaler, unsigned int value) { /* Select scaler */ regs->ctrl = (regs->ctrl & ~GRPWM_CTRL_SCSEL) | (scaler << GRPWM_CTRL_SCSEL_BIT); /* Write scaler */ regs->scaler = value; } /* Write Wave form RAM */ static void grpwm_write_wram( struct grpwm_regs *regs, unsigned int *data, int length) { unsigned int *end; volatile unsigned int *pos; pos = ®s->wram[0]; /* Write RAM */ if ( data ) { end = data + length; while ( data < end ) { *pos++ = *data++; } } else { while( length > 0 ) { *pos++ = 0; length -= 4; } } } static void grpwm_hw_reset(struct grpwm_priv *priv) { int i; struct grpwm_chan_priv *pwm; struct grpwm_regs *regs = priv->regs; /* Disable Core */ regs->ctrl = 0; /* Clear all registers */ regs->ipend = 0xffffffff; regs->wctrl = 0; /* Init all PWM channels */ for (i=0; ichannel_cnt; i++) { pwm = priv->channels[i]; pwm->pwmregs->ctrl = 0; pwm->pwmregs->period = 0; pwm->pwmregs->comp = 0; pwm->pwmregs->dbcomp = 0; pwm->pwmregs->ctrl = 0; /* Twice because METH and POL requires EN=0 */ } /* Clear RAM */ if ( priv->wave ) { grpwm_write_wram(regs, NULL, priv->wlength); } /* Set max scaler */ for (i=0; inscalers; i++) { grpwm_scaler_set(regs, i, 0xffffffff); } } /* Update one Channel but leaves the "Hold update" bit set * * A bit mask of updated bits are returned. */ static unsigned int grpwm_update_prepare_channel( struct grpwm_priv *priv, int channel, struct grpwm_ioctl_update_chan *up ) { struct grpwm_chan_priv *pwm; struct grpwm_pwm_regs *pwmregs; unsigned int ctrl; unsigned int ret; pwm = priv->channels[channel]; pwmregs = pwm->pwmregs; /* Read channel control register */ ctrl = pwmregs->ctrl; ret = 0; if ( up->options & GRPWM_UPDATE_OPTION_DISABLE ) { ctrl &= ~GRPWM_PCTRL_EN; pwmregs->ctrl = ctrl; ret |= GRPWM_PCTRL_EN; } /* Hold the updates */ if ( up->options & (GRPWM_UPDATE_OPTION_PERIOD| GRPWM_UPDATE_OPTION_COMP|GRPWM_UPDATE_OPTION_DBCOMP) ) { if ( up->options & (GRPWM_UPDATE_OPTION_PERIOD) ) { DBG("GRPWM: UPDATING 0x%x: 0x%x\n", &pwmregs->period, up->period); pwmregs->period = up->period; } if ( up->options & (GRPWM_UPDATE_OPTION_COMP) ) { DBG("GRPWM: UPDATING 0x%x: 0x%x\n", &pwmregs->comp, up->compare); pwmregs->comp = up->compare; } if ( up->options & (GRPWM_UPDATE_OPTION_DBCOMP) ) { DBG("GRPWM: UPDATING 0x%x: 0x%x\n", &pwmregs->dbcomp, up->dbcomp); pwmregs->dbcomp = up->dbcomp; } } if ( up->options & GRPWM_UPDATE_OPTION_ENABLE ) { ret |= GRPWM_PCTRL_EN; pwmregs->ctrl = ctrl | GRPWM_PCTRL_EN; } return ret; } static void grpwm_update_active(struct grpwm_priv *priv, int enable) { unsigned int ctrl; int i; ctrl = priv->regs->ctrl; /* Make all "Update Hold" bits be cleared */ ctrl &= ~GRPWM_CTRL_NOUP; /* A change in any of the Channel enable/disable bits? */ if ( enable ) { ctrl &= ~GRPWM_CTRL_EN; for(i=0; ichannel_cnt; i++) { ctrl |= priv->regs->pwms[i].ctrl & GRPWM_CTRL_EN; } } priv->regs->ctrl = ctrl; } /* Configure the hardware of a channel according to this */ static rtems_status_code grpwm_config_channel( struct grpwm_priv *priv, int channel, struct grpwm_ioctl_config *cfg ) { struct grpwm_chan_priv *pwm; unsigned int pctrl, wctrl=0; pwm = priv->channels[channel]; if ( pwm->pwmregs->ctrl & GRPWM_PCTRL_EN_BIT ) { return RTEMS_RESOURCE_IN_USE; } if ( cfg->options & ~GRPWM_CONFIG_OPTION_MASK ) { return RTEMS_INVALID_NAME; } if ( (cfg->options & GRPWM_CONFIG_OPTION_DUAL) && ((priv->regs->cap1 & GRPWM_CAP_DCM) == 0) ) { return RTEMS_INVALID_NAME; } /* IRQ set up */ pwm->isr_arg = cfg->isr_arg; pwm->isr = cfg->isr; pctrl = cfg->options | (cfg->dbscaler << GRPWM_PCTRL_DBSC_BIT) | (cfg->irqscaler << GRPWM_PCTRL_ISC_BIT) | (cfg->scaler_index << GRPWM_PCTRL_SCSEL_BIT); /* Set Wave form gerneration if available */ if ( !priv->wave || (priv->channel_cnt != (channel+1)) ) { /* Wave Form not available for this channel (or core) */ if ( cfg->wave_activate || cfg->wave_data || cfg->wave_data_length ) { return RTEMS_INVALID_NAME; } } else if ( cfg->wave_activate ) { /* Enable Wave form generation */ DBG("GRPWM: ENABLING WAVE FORM GENERATION 0x%x\n", cfg->wave_data_length); if ( cfg->wave_data ) { grpwm_write_wram(priv->regs, cfg->wave_data, cfg->wave_data_length); } /* Write length register, and let user control Wave-Sync functionality */ wctrl = (((cfg->wave_data_length-1) << GRPWM_WCTRL_STOP_BIT) & GRPWM_WCTRL_STOP); wctrl |= cfg->wave_synccfg & (GRPWM_WCTRL_WSYNCCFG|GRPWM_WCTRL_WSEN); wctrl |= (cfg->wave_sync << 16) & 0x1fff0000; priv->regs->wctrl = wctrl; /* Enable Wave form */ pctrl |= GRPWM_PCTRL_WEN; } DBG("GRPWM: CONFIG: 0x%x, WAVE CONFIG: 0x%x\n", pctrl, wctrl); pwm->pwmregs->ctrl = pctrl; return RTEMS_SUCCESSFUL; } static void grpwm_isr(void *arg) { unsigned int ipend; struct grpwm_chan_priv *pwm = arg; struct grpwm_priv *priv = pwm->common; int i; /* Get current pending interrupts */ ipend = priv->regs->ipend; for (i=0; ichannel_cnt; i++) { if ( ipend & (1<channels[i]; if ( pwm->isr ) { pwm->isr(i, pwm->isr_arg); } } } priv->regs->ipend = ipend; } static rtems_device_driver grpwm_initialize(rtems_device_major_number major, rtems_device_minor_number minor, void *arg) { return RTEMS_SUCCESSFUL; } static rtems_device_driver grpwm_open(rtems_device_major_number major, rtems_device_minor_number minor, void *arg) { struct grpwm_priv *priv; rtems_device_driver ret; struct drvmgr_dev *dev; if ( drvmgr_get_dev(&grpwm_drv_info.general, minor, &dev) ) { DBG("Wrong minor %d\n", minor); return RTEMS_INVALID_NAME; } priv = (struct grpwm_priv *)dev->priv; /* Wait until we get semaphore */ if ( rtems_semaphore_obtain(priv->dev_sem, RTEMS_WAIT, RTEMS_NO_TIMEOUT) != RTEMS_SUCCESSFUL ){ return RTEMS_INTERNAL_ERROR; } /* is device busy/taken? */ if ( priv->open ) { ret=RTEMS_RESOURCE_IN_USE; goto out; } /* Mark device taken */ priv->open = 1; ret = RTEMS_SUCCESSFUL; out: rtems_semaphore_release(priv->dev_sem); return ret; } static rtems_device_driver grpwm_close(rtems_device_major_number major, rtems_device_minor_number minor, void *arg) { struct grpwm_priv *priv; struct drvmgr_dev *dev; if ( drvmgr_get_dev(&grpwm_drv_info.general, minor, &dev) ) { return RTEMS_INVALID_NAME; } priv = (struct grpwm_priv *)dev->priv; /* Reset Hardware */ grpwm_hw_reset(priv); /* Mark Device closed */ priv->open = 0; return RTEMS_SUCCESSFUL; } static rtems_device_driver grpwm_read(rtems_device_major_number major, rtems_device_minor_number minor, void *arg) { return RTEMS_UNSATISFIED; } static rtems_device_driver grpwm_write(rtems_device_major_number major, rtems_device_minor_number minor, void *arg) { return RTEMS_UNSATISFIED; } static rtems_device_driver grpwm_ioctl(rtems_device_major_number major, rtems_device_minor_number minor, void *arg) { struct grpwm_priv *priv; struct drvmgr_dev *dev; rtems_libio_ioctl_args_t *ioarg = (rtems_libio_ioctl_args_t *)arg; if ( drvmgr_get_dev(&grpwm_drv_info.general, minor, &dev) ) { return RTEMS_INVALID_NAME; } priv = (struct grpwm_priv *)dev->priv; if (!ioarg) return RTEMS_INVALID_NAME; ioarg->ioctl_return = 0; switch(ioarg->command) { default: /* Not a valid command */ return RTEMS_NOT_DEFINED; case GRPWM_IOCTL_GET_CAP: { struct grpwm_ioctl_cap *cap = (void *)ioarg->buffer; if ( cap == NULL ) return RTEMS_INVALID_NAME; /* Copy Capability registers to user */ cap->channel_cnt = priv->channel_cnt; cap->pwm = priv->regs->cap1; cap->wave = priv->regs->cap2; break; } case GRPWM_IOCTL_SET_CONFIG: { struct grpwm_ioctl_config *cfg = (void *)ioarg->buffer; if ( cfg == NULL ) return RTEMS_INVALID_NAME; if ( cfg->channel >= priv->channel_cnt ) return RTEMS_INVALID_NAME; return grpwm_config_channel(priv, cfg->channel, cfg); } case GRPWM_IOCTL_SET_SCALER: { unsigned int invalid_mask; int i; struct grpwm_ioctl_scaler *sc = ioarg->buffer; if ( sc == NULL ) return RTEMS_INVALID_NAME; /* Test if caller reqest to set a scaler not existing */ invalid_mask = ~((1 << priv->nscalers) - 1); if ( invalid_mask & sc->index_mask ) { return RTEMS_INVALID_NAME; } /* Set scalers requested */ for (i=0; inscalers; i++) { if ( sc->index_mask & (1<regs, i, sc->values[i]); } } break; } case GRPWM_IOCTL_UPDATE: { struct grpwm_ioctl_update *up = ioarg->buffer; unsigned int invalid_mask, pctrl = 0; int i; if ( up == NULL ) return RTEMS_INVALID_NAME; /* Test if caller reqest to set a scaler not existing */ invalid_mask = ~((1 << priv->channel_cnt) - 1); if ( invalid_mask & up->chanmask ) { return RTEMS_INVALID_NAME; } /* In order for the changes to take effect at the same time, the "Hold update" * bits is set for all PWM channels that will be updated. The hold update bits * will be cleared at the same time for all channels. */ priv->regs->ctrl = (priv->regs->ctrl & ~GRPWM_CTRL_NOUP) | (up->chanmask << GRPWM_CTRL_NOUP_BIT); for (i=0; ichannel_cnt; i++) { if ( up->chanmask & (1<channels[i]); } } /* 1. Update all channels requested, * 2. Enable the core if at least one channel is enabled * 3. Disable the core if all channels are disabled */ grpwm_update_active(priv, (pctrl & GRPWM_PCTRL_EN)); break; } case GRPWM_IOCTL_IRQ: { unsigned int data = (unsigned int)ioarg->buffer; int channel = (data >> 8) & 0x7; struct grpwm_chan_priv *pwm; unsigned int pctrl; pwm = priv->channels[channel]; if ( data & GRPWM_IRQ_CLEAR ) { priv->regs->ipend |= (1<dev, pwm->irqindex); } if ( (data & 0x3) && !pwm->isr ) { /* Enable IRQ but no ISR */ return RTEMS_INVALID_NAME; } pctrl = pwm->pwmregs->ctrl & ~(GRPWM_PCTRL_IEN|GRPWM_PCTRL_IT); pctrl |= ((data & 0x3) << GRPWM_PCTRL_IEN_BIT); pwm->pwmregs->ctrl = pctrl; break; } } return RTEMS_SUCCESSFUL; } #define MAX_CHANNEL 8 char grpwm_irqindex_lookup[8][MAX_CHANNEL] = { /* Channel 1 2 3 4 5 6 7 8 */ /* npwm 1 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* npwm 2 */ {0, 1, 0, 0, 0, 0, 0, 0}, /* npwm 3 */ {0, 0, 0, 0, 0, 0, 0, 0}, /* npwm 4 */ {0, 0, 0, 1, 0, 0, 0, 0}, /* npwm 5 */ {0, 0, 0, 1, 2, 0, 0, 0}, /* npwm 6 */ {0, 0, 0, 1, 1, 1, 0, 0}, /* npwm 7 */ {0, 0, 0, 1, 1, 1, 2, 0}, /* npwm 8 */ {0, 0, 0, 1, 1, 1, 2, 3} }; int grpwm_device_init(struct grpwm_priv *priv) { struct amba_dev_info *ambadev; struct ambapp_core *pnpinfo; int mask, i, sepirq; unsigned int wabits; struct grpwm_chan_priv *pwm; struct grpwm_regs *regs; /* Get device information from AMBA PnP information */ ambadev = (struct amba_dev_info *)priv->dev->businfo; if ( ambadev == NULL ) { return -1; } pnpinfo = &ambadev->info; priv->irq = pnpinfo->irq; regs = priv->regs = (struct grpwm_regs *)pnpinfo->apb_slv->start; DBG("GRPWM: 0x%08x irq %d\n", (unsigned int)regs, priv->irq); /* Disable Core */ regs->ctrl = 0; /* Clear all registers */ regs->ipend = 0xffffffff; regs->wctrl = 0; /* Find the number of PWM channels */ priv->channel_cnt = 1 + ((regs->cap1 & GRPWM_CAP_NPWM) >> GRPWM_CAP_NPWM_BIT); pwm = malloc(sizeof(*pwm)*priv->channel_cnt); if ( !pwm ) return -1; memset(pwm, 0, sizeof(*pwm)*priv->channel_cnt); /* Init all PWM channels */ sepirq = ((regs->cap1 & GRPWM_CAP_SEP) >> GRPWM_CAP_SEP_BIT); for (i=0; ichannel_cnt; i++, pwm++) { priv->channels[i] = pwm; pwm->common = priv; pwm->pwmregs = ®s->pwms[i]; if ( sepirq == 0 ) { pwm->irqindex = 0; } else if ( sepirq == 1 ) { pwm->irqindex = i; } else { pwm->irqindex = grpwm_irqindex_lookup[priv->channel_cnt][i]; } } /* Detect if Wave Form capability is availble for last PWM channel */ if ( regs->cap2 & GRPWM_CAP2_WPWM ) { priv->wave = 1; /* Clear RAM */ wabits = (regs->cap2 & GRPWM_CAP2_WABITS) >> GRPWM_CAP2_WABITS_BIT; priv->wlength = 1 << wabits; } priv->nscalers = 1 + ((regs->cap1 & GRPWM_CAP_NSC) >> GRPWM_CAP_NSC_BIT); grpwm_hw_reset(priv); /* Device Semaphore created with count = 1 */ if ( rtems_semaphore_create(rtems_build_name('G', 'P', 'W', 'M'), 1, RTEMS_FIFO|RTEMS_SIMPLE_BINARY_SEMAPHORE|RTEMS_NO_INHERIT_PRIORITY|\ RTEMS_LOCAL|RTEMS_NO_PRIORITY_CEILING, 0, &priv->dev_sem) != RTEMS_SUCCESSFUL ) { return -1; } /* Register interrupt handler for all PWM channels */ mask = 0; for (i=0; ichannel_cnt; i++) { pwm = priv->channels[i]; if ( (mask & (1 << pwm->irqindex)) == 0 ) { /* Not registered interrupt handler for this IRQ index before, * we do it now. */ mask |= (1 << pwm->irqindex); drvmgr_interrupt_register( priv->dev, pwm->irqindex, "grpwm", grpwm_isr, pwm); } } return 0; }