/* Copyright Jeroen Vreeken (jeroen@vreeken.net), 2014 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 3 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, see . */ #include "am335x.h" #include #include #include struct controller_block_private { float *in; float tbprd; // period double freq; bool sym; void *base; }; static void pwm_calculate(struct controller_block *pwm) { struct controller_block_private *priv = pwm->private; float in; uint16_t cmp; float tbprd; bool sign; void *base = priv->base; tbprd = priv->tbprd; in = *priv->in; sign = signbit(in); cmp = fminf(fabsf(in) * tbprd, tbprd); if (sign) { am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPA, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPB, cmp); } else { am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPA, cmp); am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPB, 0); } } static void set_freq(struct controller_block *pwm) { struct controller_block_private *priv = pwm->private; void *base = priv->base; int clkdiv = 0; int div = 1; double period, freq; uint16_t tbprd; uint16_t aqctl; /* double divider for symmetric use */ period = AM335X_SYSCLK / (priv->freq * ( priv->sym ? 2.0 : 1.0)); while (period > AM335X_PWMSS_EPWM_TBPRD_MAX) { clkdiv++; div *= 2; period /= 2.0; if (clkdiv == AM335X_PWMSS_EPWM_TBCTL_CLKDIV_MAX) break; } tbprd = period; am335x_write16(base, AM335X_PWMSS_REG_EPWM_TBCTL, AM335X_PWMSS_EPWM_TBCTL_CLKDIV_SET(clkdiv) | AM335X_PWMSS_EPWM_TBCTL_SYNC0SEL_DIS | (priv->sym ? AM335X_PWMSS_EPWM_TBCTL_CTRMODE_UD : AM335X_PWMSS_EPWM_TBCTL_CTRMODE_DN)); am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPCTL, 0); if (priv->sym) { aqctl = AM335X_PWMSS_EPWM_AQCTLA_CBD_NOP | AM335X_PWMSS_EPWM_AQCTLA_CBU_NOP | AM335X_PWMSS_EPWM_AQCTLA_CAD_SET | AM335X_PWMSS_EPWM_AQCTLA_CAU_CLR | AM335X_PWMSS_EPWM_AQCTLA_PRD_NOP; } else { aqctl = AM335X_PWMSS_EPWM_AQCTLA_CBD_NOP | AM335X_PWMSS_EPWM_AQCTLA_CBU_NOP | AM335X_PWMSS_EPWM_AQCTLA_CAD_SET | AM335X_PWMSS_EPWM_AQCTLA_CAU_CLR | AM335X_PWMSS_EPWM_AQCTLA_PRD_CLR; } am335x_write16(base, AM335X_PWMSS_REG_EPWM_AQCTLA, aqctl); am335x_write16(base, AM335X_PWMSS_REG_EPWM_AQCTLB, aqctl); am335x_write16(base, AM335X_PWMSS_REG_EPWM_AQSFRC, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_TBPRD, tbprd); am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPA, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_CMPB, 0); priv->tbprd = tbprd; freq = AM335X_SYSCLK / (tbprd * div * ( priv->sym ? 2.0 : 1.0)); log_send(LOG_T_DEBUG, "%s: Frequency: %gHz, period: %d ticks, divider: %d", pwm->name, freq, tbprd, div); priv->freq = freq; } static int param_set_freq(struct controller_block *pwm, char *param, int argc, va_list val) { pwm->private->freq = va_arg(val, double); set_freq(pwm); return 0; } static int param_set_sym(struct controller_block *pwm, char *param, int argc, va_list val) { pwm->private->sym = va_arg(val, int); set_freq(pwm); return 0; } static struct controller_block_param_list params[] = { { "frequency", false, param_set_freq, .args = { "double", NULL } }, { "symmetrical", false, param_set_sym, .args = { "int", NULL } }, { NULL }, }; static struct controller_block_interm_list interms[] = { { "in", CONTROLLER_BLOCK_TERM_FLOAT, offsetof(struct controller_block_private, in) }, { NULL }, }; static struct controller_block * block_am335x_pwm_create(char *name, int argc, va_list ap) { struct controller_block *pwm; void *base; uint32_t reg; int pwm_nr; size_t offset; size_t cm_offset; size_t pin_a; size_t pin_b; unsigned mode_a; unsigned mode_b; uint32_t ctrl_bits; pwm_nr = va_arg(ap, int); if (pwm_nr < 0 || pwm_nr > 2) { log_send(LOG_T_ERROR, "%s: pwm%d is not valid. (valid: 0-2)", name, pwm_nr); return NULL; } switch (pwm_nr) { case 0: offset = AM335X_PWMSS0_BASE; cm_offset = AM335X_CM_PER_EPWMSS0_CLKCTRL; pin_a = AM335X_CONTROL_MODULE_CONF_MCASP0_ACLKX; pin_b = AM335X_CONTROL_MODULE_CONF_MCASP0_FSX; mode_a = 1; mode_b = 1; ctrl_bits = AM335X_CONTROL_MODULE_PWMSS0_TBCLKEN; break; case 1: offset = AM335X_PWMSS1_BASE; cm_offset = AM335X_CM_PER_EPWMSS1_CLKCTRL; pin_a = AM335X_CONTROL_MODULE_CONF_LCD_DATA10; pin_b = AM335X_CONTROL_MODULE_CONF_LCD_DATA11; mode_a = 2; mode_b = 2; ctrl_bits = AM335X_CONTROL_MODULE_PWMSS1_TBCLKEN; break; case 2: offset = AM335X_PWMSS2_BASE; cm_offset = AM335X_CM_PER_EPWMSS2_CLKCTRL; pin_a = AM335X_CONTROL_MODULE_CONF_LCD_DATA0; pin_b = AM335X_CONTROL_MODULE_CONF_LCD_DATA1; mode_a = 3; mode_b = 3; ctrl_bits = AM335X_CONTROL_MODULE_PWMSS2_TBCLKEN; break; } if (am335x_cm_enable(AM335X_CM_PER_L4LS_CLKCTRL)) { log_send(LOG_T_ERROR, "%s: Enabling L4LS clock failed", name); return NULL; } if (am335x_control_module_set(AM335X_CONTROL_MODULE_PWMSS_CTRL, ctrl_bits)) { log_send(LOG_T_ERROR, "%s: Enabling TBCLK in control module failed", name); return NULL; } if (am335x_cm_enable(cm_offset)) { log_send(LOG_T_ERROR, "%s: Enabling module failed", name); return NULL; } if (am335x_pinmux_set(pin_a, mode_a, AM335X_CONTROL_MODULE_PINMUX_SLEWCTRL_FAST | AM335X_CONTROL_MODULE_PINMUX_RX)) { log_send(LOG_T_ERROR, "%s: Could not set mux for pin A", name); return NULL; } if (am335x_pinmux_set(pin_b, mode_b, AM335X_CONTROL_MODULE_PINMUX_SLEWCTRL_FAST | AM335X_CONTROL_MODULE_PINMUX_RX)) { log_send(LOG_T_ERROR, "%s: Could not set mux for pin B", name); return NULL; } base = am335x_mem(offset, AM335X_PWMSS_SIZE); if (!base) { log_send(LOG_T_ERROR, "%s: Mapping PWM failed", name); return NULL; } log_send(LOG_T_DEBUG, "%s: PWM mapped @ %p", name, base); reg = am335x_read32(base, AM335X_PWMSS_REG_IDVER); log_send(LOG_T_DEBUG, "%s: IDVER: 0x%" PRIx32 ": func: 0x%03x rev: %d.%d", name, reg, AM335X_PWMSS_IDVER_FUNC_GET(reg), AM335X_PWMSS_IDVER_X_MAJOR_GET(reg), AM335X_PWMSS_IDVER_Y_MINOR_GET(reg)); if (AM335X_PWMSS_IDVER_FUNC_GET(reg) != AM335X_PWMSS_IDVER_FUNC) { log_send(LOG_T_ERROR, "Unexpected functional number"); goto err_rev; } am335x_write16(base, AM335X_PWMSS_REG_EPWM_PCCTL, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_DBCTL, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_TZCTL, AM335X_PWMSS_EPWM_TZCTL_TZA_NOP | AM335X_PWMSS_EPWM_TZCTL_TZB_NOP ); am335x_write16(base, AM335X_PWMSS_REG_EPWM_TZEINT, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_ETSEL, 0); am335x_write16(base, AM335X_PWMSS_REG_EPWM_HRCNFG, 0); if (!(pwm = controller_block_alloc("am355x_pwm", name, sizeof(struct controller_block_private)))) goto err_alloc; if (controller_block_interm_list_init(pwm, interms)) goto err_interm; pwm->private->base = base; pwm->private->sym = true; pwm->private->freq = 10000; set_freq(pwm); am335x_write32(base, AM335X_PWMSS_REG_CLKCONFIG, (am335x_read32(base, AM335X_PWMSS_REG_CLKCONFIG) & ~AM335X_PWMSS_CLKCONFIG_EPWMCLKSTOP_REQ) | AM335X_PWMSS_CLKCONFIG_EPWMCLK_EN); pwm->calculate = pwm_calculate; if (controller_block_param_list_add(pwm, params)) goto err_param; if (controller_block_add(pwm)) goto err_add; return pwm; err_add: err_param: err_interm: controller_block_free(pwm); err_alloc: err_rev: return NULL; } BLOCK_CREATE(am335x_pwm) = { .create = block_am335x_pwm_create, .args = { "int", NULL }, };