misc: pm8058-pwm: add two APIs
These two new APIs allow a driver to specify its PWM period
and duty cycle separately using PM8058 chip specific parameters
or raw values. The API to program a duty cycle uses hardware
module's LUT (LookUpTable) to output PWM value, which can be
used to alleviate potential glitches using direct writing to
PWM register.
Signed-off-by: Willie Ruan <wruan@codeaurora.org>
diff --git a/drivers/misc/pmic8058-pwm.c b/drivers/misc/pmic8058-pwm.c
index 451bb56..93d89f0 100644
--- a/drivers/misc/pmic8058-pwm.c
+++ b/drivers/misc/pmic8058-pwm.c
@@ -173,6 +173,8 @@
#define MIN_MPT ((PRE_DIVIDE_MIN * CLK_PERIOD_MIN) << PM8058_PWM_M_MIN)
#define MAX_MPT ((PRE_DIVIDE_MAX * CLK_PERIOD_MAX) << PM8058_PWM_M_MAX)
+#define CHAN_LUT_SIZE (PM_PWM_LUT_SIZE / PM8058_PWM_CHANNELS)
+
/* Private data */
struct pm8058_pwm_chip;
@@ -183,7 +185,7 @@
struct pm8058_pwm_period period;
int pwm_value;
int pwm_period;
- int pwm_duty;
+ int use_lut; /* Use LUT to output PWM */
u8 pwm_ctl[PM8058_LPG_CTL_REGS];
int irq;
struct pm8058_pwm_chip *chip;
@@ -578,6 +580,7 @@
if (!pwm->in_use) {
pwm->in_use = 1;
pwm->label = label;
+ pwm->use_lut = 0;
if (pwm_chip->pdata && pwm_chip->pdata->config)
pwm_chip->pdata->config(pwm, pwm_id, 1);
@@ -714,7 +717,106 @@
}
EXPORT_SYMBOL(pwm_disable);
-/*
+/**
+ * pm8058_pwm_config_period - change PWM period
+ *
+ * @pwm: the PWM device
+ * @pwm_p: period in struct pm8058_pwm_period
+ */
+int pm8058_pwm_config_period(struct pwm_device *pwm,
+ struct pm8058_pwm_period *period)
+{
+ int rc;
+
+ if (pwm == NULL || IS_ERR(pwm) || period == NULL)
+ return -EINVAL;
+ if (pwm->chip == NULL)
+ return -ENODEV;
+
+ mutex_lock(&pwm->chip->pwm_mutex);
+
+ if (!pwm->in_use) {
+ rc = -EINVAL;
+ goto out_unlock;
+ }
+
+ pwm->period.pwm_size = period->pwm_size;
+ pwm->period.clk = period->clk;
+ pwm->period.pre_div = period->pre_div;
+ pwm->period.pre_div_exp = period->pre_div_exp;
+
+ pm8058_pwm_save_period(pwm);
+ pm8058_pwm_bank_sel(pwm);
+ rc = pm8058_pwm_write(pwm, 4, 6);
+
+out_unlock:
+ mutex_unlock(&pwm->chip->pwm_mutex);
+ return rc;
+}
+EXPORT_SYMBOL(pm8058_pwm_config_period);
+
+/**
+ * pm8058_pwm_config_duty_cycle - change PWM duty cycle
+ *
+ * @pwm: the PWM device
+ * @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size)
+ */
+int pm8058_pwm_config_duty_cycle(struct pwm_device *pwm, int pwm_value)
+{
+ struct pm8058_pwm_lut lut;
+ int flags, start_idx;
+ int rc = 0;
+
+ if (pwm == NULL || IS_ERR(pwm))
+ return -EINVAL;
+ if (pwm->chip == NULL)
+ return -ENODEV;
+
+ mutex_lock(&pwm->chip->pwm_mutex);
+
+ if (!pwm->in_use || !pwm->pwm_period) {
+ rc = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (pwm->pwm_value == pwm_value)
+ goto out_unlock;
+
+ pwm->pwm_value = pwm_value;
+ flags = PM_PWM_LUT_RAMP_UP;
+
+ start_idx = pwm->pwm_id * CHAN_LUT_SIZE;
+ pm8058_pwm_change_table(pwm, &pwm_value, start_idx, 1, 1);
+
+ if (!pwm->use_lut) {
+ pwm->use_lut = 1;
+
+ lut.lut_duty_ms = 1;
+ lut.lut_lo_index = start_idx;
+ lut.lut_hi_index = start_idx;
+ lut.lut_pause_lo = 0;
+ lut.lut_pause_hi = 0;
+ lut.flags = flags;
+
+ rc = pm8058_pwm_change_lut(pwm, &lut);
+ } else {
+ pm8058_pwm_save_index(pwm, start_idx, start_idx, flags);
+ pm8058_pwm_save(&pwm->pwm_ctl[1], PM8058_PWM_BYPASS_LUT, 0);
+
+ pm8058_pwm_bank_sel(pwm);
+ rc = pm8058_pwm_write(pwm, 0, 3);
+ }
+
+ if (rc)
+ pr_err("[%d]: pm8058_pwm_write: rc=%d\n", pwm->pwm_id, rc);
+
+out_unlock:
+ mutex_unlock(&pwm->chip->pwm_mutex);
+ return rc;
+}
+EXPORT_SYMBOL(pm8058_pwm_config_duty_cycle);
+
+/**
* pm8058_pwm_lut_config - change a PWM device configuration to use LUT
*
* @pwm: the PWM device
@@ -726,7 +828,6 @@
* @pause_lo: pause time in millisecond at low index
* @pause_hi: pause time in millisecond at high index
* @flags: control flags
- *
*/
int pm8058_pwm_lut_config(struct pwm_device *pwm, int period_us,
int duty_pct[], int duty_time_ms, int start_idx,
@@ -790,7 +891,7 @@
}
EXPORT_SYMBOL(pm8058_pwm_lut_config);
-/*
+/**
* pm8058_pwm_lut_enable - control a PWM device to start/stop LUT ramp
*
* @pwm: the PWM device