/*
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 },
};