mfd: pm8xxx-pwm: add pm8xxx PWM driver
Subsystems (2)
PATCH 1/2 mfd: pm8xxx-pwm: add pm8xxx PWM driver
Qualcomm PM8xxx chips, such as PM8058 and PM8921, have 8 channels of PWM, also called LPG (Light Pulse Generator) in HW specs. All PWM channels can be used as simple PWM machine or as a more advanced PWM pattern generator using programmed lookup table.
This patch supports all APIs listed in <linux/pwm.h> with a small difference. The two parameters (duty_ns and period_ns) in pwm_config() are used as values in microseconds instead of nanoseconds. Otherwise a 32-bit integer can't fit for a range of 7 us to 300+ seconds.
--- drivers/mfd/Kconfig | 10 + drivers/mfd/Makefile | 1 + drivers/mfd/pm8xxx-pwm.c | 819 ++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/pm8xxx/pwm.h | 88 +++++ 4 files changed, 918 insertions(+), 0 deletions(-) create mode 100644 drivers/mfd/pm8xxx-pwm.c create mode 100644 include/linux/mfd/pm8xxx/pwm.h
drivers/mfd/Kconfig +10 -0
@@ -699,6 +699,16 @@ config MFD_PM8XXX_IRQ This is required to use certain other PM 8xxx features, such as GPIO and MPP. +config MFD_PM8XXX_PWM + tristate "Support for Qualcomm PM8xxx PWM feature" + depends on MFD_PM8XXX + default y if MFD_PM8XXX + help + This is the Pulse Width Modulation (PWM) driver for Qualcomm + PM 8xxx PMIC chips. It can driver 8 channels of PWM output, and + has a lookup table with size of 64 to be shared by any of the + 8 channels. + endif # MFD_SUPPORT menu "Multimedia Capabilities Port drivers"
drivers/mfd/Makefile +1 -0
@@ -90,3 +90,4 @@ obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o obj-$(CONFIG_MFD_PM8XXX_IRQ) += pm8xxx-irq.o +obj-$(CONFIG_MFD_PM8XXX_PWM) += pm8xxx-pwm.o
drivers/mfd/pm8xxx-pwm.c new file +819 -0
819 lines not on this page · raw message
include/linux/mfd/pm8xxx/pwm.h new file +88 -0
88 lines not on this page · raw message
Signature · 5 lines
1.6.5.2 -- Sent by an employee of the Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
PATCH 2/2 mfd: pm8921-core: add pwm device for PM8921
Qualcomm PM8921 has a PWM module which can output 8 channels of PWM signals.
--- drivers/mfd/pm8921-core.c | 11 +++++++++++ include/linux/mfd/pm8xxx/pm8921.h | 1 + 2 files changed, 12 insertions(+), 0 deletions(-)
drivers/mfd/pm8921-core.c +11 -0
@@ -107,6 +107,11 @@ static const struct resource mpp_cell_resources[] __devinitconst = { }, }; +static struct mfd_cell pwm_cell__devinitdata = { + .name =PM8XXX_PWM_DEV_NAME , + .id =-1 , +}; +static struct mfd_cell mpp_cell__devinitdata = { .name =PM8XXX_MPP_DEV_NAME , .id =-1 ,
@@ -160,6 +165,12 @@ static int __devinit pm8921_add_subdevices(const struct pm8921_platform_data } } + ret =mfd_add_devices (pmic->dev,0 , &pwm_cell,1 ,NULL ,0 ); +if (ret) { +pr_err ("Failed to add pwm subdevice ret=%d\n" , ret); + goto bail; + } +return 0 ; bail:if (pmic->irq_chip) {
include/linux/mfd/pm8xxx/pm8921.h +1 -0
@@ -22,6 +22,7 @@#include <linux/mfd/pm8xxx/irq.h> #include <linux/mfd/pm8xxx/gpio.h> #include <linux/mfd/pm8xxx/mpp.h> +#include <linux/mfd/pm8xxx/pwm.h> #define PM8921_NR_IRQS 256
Signature · 5 lines
1.6.5.2 -- Sent by an employee of the Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
Hi Willie,
Quoted 9 lines · Willie Ruan
Qualcomm PM8xxx chips, such as PM8058 and PM8921, have 8 channels of PWM, also called LPG (Light Pulse Generator) in HW specs. All PWM channels can be used as simple PWM machine or as a more advanced PWM pattern generator using programmed lookup table.
This patch supports all APIs listed in <
linux/pwm.h> with a small difference. The two parameters (duty_nsandperiod_ns) inpwm_config()are used as values in microseconds instead of nanoseconds. Otherwise a 32-bit integer can't fit for a range of 7 us to 300+ seconds.
I can only lament the fact that we're still lacking a proper PWM API. Besides that, I have a couple comments:
Quoted 58 lines
drivers/mfd/pm8xxx-pwm.c new file +58
-0+static void pm8xxx_pwm_calc_period (unsigned int period_us, +struct pm8xxx_pwm_config *pwm_conf) +{ +int n, m, clk, div; +int best_m, best_div, best_clk; +int last_err, cur_err, better_err, better_m; +unsigned int tmp_p, last_p, min_err, period_n; + +/* PWM Period / N */ +if (period_us < (40 *USEC_PER_SEC )) {/* ~6-bit max */ + period_n = (period_us *NSEC_PER_USEC ) >>6 ; + n =6 ; + }else if (period_us < (274 *USEC_PER_SEC )) {/* overflow threshold */ + period_n = (period_us >>6 ) *NSEC_PER_USEC ; +if (period_n >=MAX_MPT ) { + n =9 ; + period_n >>=3 ; + }else + n =6 ; + }else { + period_n = (period_us >>9 ) *NSEC_PER_USEC ; + n =9 ; + } + + min_err =MAX_MPT ; + best_m =0 ; + best_clk =0 ; + best_div =0 ; +for (clk =0 ; clk <NUM_CLOCKS ; clk++) { +for (div =0 ; div <NUM_PRE_DIVIDE ; div++) { + tmp_p = period_n; + last_p = tmp_p; +for (m =0 ; m <=PM8XXX_PWM_M_MAX ; m++) { +if (tmp_p <= pt_t[div][clk]) { +/* Found local best */ +if (!m) { + better_err = pt_t[div][clk] - + tmp_p; + better_m = m; + }else { + last_err = last_p - + pt_t[div][clk]; + cur_err = pt_t[div][clk] - + tmp_p; + +if (cur_err < last_err) { + better_err = cur_err; + better_m = m; + }else { + better_err = last_err; + better_m = m -1 ; + } + } + +if (better_err < min_err) { + min_err = better_err; + best_m = better_m; + best_clk = clk;
drivers/mfd/pm8xxx-pwm.c line 339
Quoted 10 lines
drivers/mfd/pm8xxx-pwm.c new file +10
-0+ best_div = div; + } +break ; + }else { + last_p = tmp_p; + tmp_p >>=1 ; + } + } + } + }
This loop needs to be splitted up into 2-3 different routines. Your code will be much more readable.
Quoted 102 lines
drivers/mfd/pm8xxx-pwm.c new file +102
-0+static int pm8xxx_pwm_configure (struct pwm_device *pwm, +struct pm8xxx_pwm_config *pwm_conf) +{ +int i, rc, len; +u8 reg, ramp_enabled =0 ; + + reg = (pwm_conf->pwm_size >6 ) ?PM8XXX_PWM_SIZE_9_BIT :0 ; + pwm->pwm_ctl[5 ] = reg; + + reg = ((pwm_conf->clk +1 ) <<PM8XXX_PWM_CLK_SEL_SHIFT ) + &PM8XXX_PWM_CLK_SEL_MASK ; + reg |= (pwm_conf->pre_div <<PM8XXX_PWM_PREDIVIDE_SHIFT ) + &PM8XXX_PWM_PREDIVIDE_MASK ; + reg |= pwm_conf->pre_div_exp &PM8XXX_PWM_M_MASK ; + pwm->pwm_ctl[4 ] = reg; + +if (pwm_conf->bypass_lut) { + pwm->pwm_ctl[0 ] &=PM8XXX_PWM_PWM_START ;/* keep enabled */ + pwm->pwm_ctl[1 ] =PM8XXX_PWM_BYPASS_LUT ; + pwm->pwm_ctl[2 ] =0 ; + +if (pwm_conf->pwm_size >6 ) { + pwm->pwm_ctl[3 ] = pwm_conf->pwm_value + &PM8XXX_PWM_VALUE_BIT7_0 ; + pwm->pwm_ctl[4 ] |= (pwm_conf->pwm_value >>1 ) + &PM8XXX_PWM_VALUE_BIT8 ; + }else { + pwm->pwm_ctl[3 ] = pwm_conf->pwm_value + &PM8XXX_PWM_VALUE_BIT5_0 ; + } + + len =6 ; + }else { +int pause_cnt, j; + +/* Linear search for duty time */ +for (i =0 ; i <PM8XXX_PWM_1KHZ_COUNT_MAX ; i++) { +if (duty_msec[i] >= pwm_conf->lut_duty_ms) +break ; + } + + ramp_enabled = pwm->pwm_ctl[0 ] &PM8XXX_PWM_RAMP_GEN_START ; + pwm->pwm_ctl[0 ] &=PM8XXX_PWM_PWM_START ;/* keep enabled */ + pwm->pwm_ctl[0 ] |= (i <<PM8XXX_PWM_1KHZ_COUNT_SHIFT ) & +PM8XXX_PWM_1KHZ_COUNT_MASK ; + pwm->pwm_ctl[1 ] = pwm_conf->lut_hi_index & +PM8XXX_PWM_HIGH_INDEX_MASK ; + pwm->pwm_ctl[2 ] = pwm_conf->lut_lo_index & +PM8XXX_PWM_LOW_INDEX_MASK ; + +if (pwm_conf->flags &PM_PWM_LUT_REVERSE ) + pwm->pwm_ctl[1 ] |=PM8XXX_PWM_REVERSE_EN ; +if (pwm_conf->flags &PM_PWM_LUT_RAMP_UP ) + pwm->pwm_ctl[2 ] |=PM8XXX_PWM_RAMP_UP ; +if (pwm_conf->flags &PM_PWM_LUT_LOOP ) + pwm->pwm_ctl[2 ] |=PM8XXX_PWM_LOOP_EN ; + +/* Pause time */ +if (pwm_conf->flags &PM_PWM_LUT_PAUSE_HI_EN ) { +/* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_hi + duty_msec[i] /2 ) + / duty_msec[i]; +for (j =0 ; j <PM8XXX_PWM_PAUSE_COUNT_MAX ; j++) { +if (pause_count[j] >= pause_cnt) +break ; + } + pwm->pwm_ctl[5 ] = (j << +PM8XXX_PWM_PAUSE_COUNT_HI_SHIFT ) & +PM8XXX_PWM_PAUSE_COUNT_HI_MASK ; + pwm->pwm_ctl[5 ] |=PM8XXX_PWM_PAUSE_ENABLE_HIGH ; + }else + pwm->pwm_ctl[5 ] =0 ; + +if (pwm_conf->flags &PM_PWM_LUT_PAUSE_LO_EN ) { +/* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_lo + duty_msec[i] /2 ) + / duty_msec[i]; +for (j =0 ; j <PM8XXX_PWM_PAUSE_COUNT_MAX ; j++) { +if (pause_count[j] >= pause_cnt) +break ; + } + pwm->pwm_ctl[6 ] = (j << +PM8XXX_PWM_PAUSE_COUNT_LO_SHIFT ) & +PM8XXX_PWM_PAUSE_COUNT_LO_MASK ; + pwm->pwm_ctl[6 ] |=PM8XXX_PWM_PAUSE_ENABLE_LOW ; + }else + pwm->pwm_ctl[6 ] =0 ; + + len =7 ; + } + +pm8xxx_pwm_bank_sel (pwm); + +for (i =0 ; i < len; i++) { + rc =pm8xxx_writeb (pwm->chip->dev->parent, +SSBI_REG_ADDR_LPG_CTL (i), + pwm->pwm_ctl[i]); +if (rc) { +pr_err ("pm8xxx_write(): rc=%d (PWM Ctl[%d])\n" , rc, i); +break ; + } + }
drivers/mfd/pm8xxx-pwm.c line 458
Quoted 10 lines
drivers/mfd/pm8xxx-pwm.c new file +10
-0+ +if (ramp_enabled) { + pwm->pwm_ctl[0 ] |= ramp_enabled; +pm8xxx_writeb (pwm->chip->dev->parent, +SSBI_REG_ADDR_LPG_CTL (0 ), + pwm->pwm_ctl[0 ]); + } + +return rc; +}
I would also appreciate if this routine could be made smaller by having it calling sub routines.
Cheers,
Samuel.
Signature · 2 lines
Intel Open Source Technology Centre http://oss.intel.com/
Hi Samuel,
Thank you for reviewing my patches. I'll make the two functions smaller by creating sub routines as you suggested.
pm8xxx_pwm_calc_period()pm8xxx_pwm_configure()
Other comments below.
Quoted 8 lines
Hi Willie,
Quoted 5 lines · Willie Ruan
Qualcomm PM8xxx chips, such as PM8058 and PM8921, have 8 channels of PWM, also called LPG (Light Pulse Generator) in HW specs. All PWM channels can be used as simple PWM machine or as a more advanced PWM pattern generator using programmed lookup table.
Quoted 7 lines
Quoted 4 lines
This patch supports all APIs listed in <
linux/pwm.h> with a small difference. The two parameters (duty_nsandperiod_ns) inpwm_config()are used as values in microseconds instead of nanoseconds. Otherwise a 32-bit integer can't fit for a range of 7 us to 300+ seconds.I can only lament the fact that we're still lacking a proper PWM API. Besides
that, I have a couple comments:
I agree. Hopefully someone would create a better PWM API soon for us to use. Fortunately PWM driver and API are not hard for our customers.
Quoted 64 lines
Quoted 64 lines
drivers/mfd/pm8xxx-pwm.c new file +13
-0+static void pm8xxx_pwm_calc_period (unsigned int period_us, +struct pm8xxx_pwm_config *pwm_conf) +{ +int n, m, clk, div; +int best_m, best_div, best_clk; +int last_err, cur_err, better_err, better_m; +unsigned int tmp_p, last_p, min_err, period_n; + +/* PWM Period / N */ +if (period_us < (40 *USEC_PER_SEC )) {/* ~6-bit max */ + period_n = (period_us *NSEC_PER_USEC ) >>6 ; + n =6 ; + }else if (period_us < (274 *USEC_PER_SEC )) {/* overflow threshold */
drivers/mfd/pm8xxx-pwm.c new file +49
-0+ period_n = (period_us >>6 ) *NSEC_PER_USEC ; +if (period_n >=MAX_MPT ) { + n =9 ; + period_n >>=3 ; + }else + n =6 ; + }else { + period_n = (period_us >>9 ) *NSEC_PER_USEC ; + n =9 ; + } + + min_err =MAX_MPT ; + best_m =0 ; + best_clk =0 ; + best_div =0 ; +for (clk =0 ; clk <NUM_CLOCKS ; clk++) { +for (div =0 ; div <NUM_PRE_DIVIDE ; div++) { + tmp_p = period_n; + last_p = tmp_p; +for (m =0 ; m <=PM8XXX_PWM_M_MAX ; m++) { +if (tmp_p <= pt_t[div][clk]) { +/* Found local best */ +if (!m) { + better_err = pt_t[div][clk] - + tmp_p; + better_m = m; + }else { + last_err = last_p - + pt_t[div][clk]; + cur_err = pt_t[div][clk] - + tmp_p; + +if (cur_err < last_err) { + better_err = cur_err; + better_m = m; + }else { + better_err = last_err; + better_m = m -1 ; + } + } + +if (better_err < min_err) { + min_err = better_err; + best_m = better_m; + best_clk = clk; + best_div = div; + } +break ; + }else {
Quoted 8 lines
drivers/mfd/pm8xxx-pwm.c line 339
Quoted 6 lines
drivers/mfd/pm8xxx-pwm.c new file +6
-0+ last_p = tmp_p; + tmp_p >>=1 ; + } + } + } + }This loop needs to be splitted up into 2-3 different routines. Your code will be much more readable.
Sure I can change this.
Quoted 107 lines
Quoted 107 lines
drivers/mfd/pm8xxx-pwm.c new file +106
-0+static int pm8xxx_pwm_configure (struct pwm_device *pwm, +struct pm8xxx_pwm_config *pwm_conf) +{ +int i, rc, len; +u8 reg, ramp_enabled =0 ; + + reg = (pwm_conf->pwm_size >6 ) ?PM8XXX_PWM_SIZE_9_BIT :0 ; + pwm->pwm_ctl[5 ] = reg; + + reg = ((pwm_conf->clk +1 ) <<PM8XXX_PWM_CLK_SEL_SHIFT ) + &PM8XXX_PWM_CLK_SEL_MASK ; + reg |= (pwm_conf->pre_div <<PM8XXX_PWM_PREDIVIDE_SHIFT ) + &PM8XXX_PWM_PREDIVIDE_MASK ; + reg |= pwm_conf->pre_div_exp &PM8XXX_PWM_M_MASK ; + pwm->pwm_ctl[4 ] = reg; + +if (pwm_conf->bypass_lut) { + pwm->pwm_ctl[0 ] &=PM8XXX_PWM_PWM_START ;/* keep enabled */ + pwm->pwm_ctl[1 ] =PM8XXX_PWM_BYPASS_LUT ; + pwm->pwm_ctl[2 ] =0 ; + +if (pwm_conf->pwm_size >6 ) { + pwm->pwm_ctl[3 ] = pwm_conf->pwm_value + &PM8XXX_PWM_VALUE_BIT7_0 ; + pwm->pwm_ctl[4 ] |= (pwm_conf->pwm_value >>1 ) + &PM8XXX_PWM_VALUE_BIT8 ; + }else { + pwm->pwm_ctl[3 ] = pwm_conf->pwm_value + &PM8XXX_PWM_VALUE_BIT5_0 ; + } + + len =6 ; + }else { +int pause_cnt, j; + +/* Linear search for duty time */ +for (i =0 ; i <PM8XXX_PWM_1KHZ_COUNT_MAX ; i++) { +if (duty_msec[i] >= pwm_conf->lut_duty_ms) +break ; + } + + ramp_enabled = pwm->pwm_ctl[0 ] &PM8XXX_PWM_RAMP_GEN_START ; + pwm->pwm_ctl[0 ] &=PM8XXX_PWM_PWM_START ;/* keep enabled */ + pwm->pwm_ctl[0 ] |= (i <<PM8XXX_PWM_1KHZ_COUNT_SHIFT ) & +PM8XXX_PWM_1KHZ_COUNT_MASK ; + pwm->pwm_ctl[1 ] = pwm_conf->lut_hi_index & +PM8XXX_PWM_HIGH_INDEX_MASK ; + pwm->pwm_ctl[2 ] = pwm_conf->lut_lo_index & +PM8XXX_PWM_LOW_INDEX_MASK ; + +if (pwm_conf->flags &PM_PWM_LUT_REVERSE ) + pwm->pwm_ctl[1 ] |=PM8XXX_PWM_REVERSE_EN ; +if (pwm_conf->flags &PM_PWM_LUT_RAMP_UP ) + pwm->pwm_ctl[2 ] |=PM8XXX_PWM_RAMP_UP ; +if (pwm_conf->flags &PM_PWM_LUT_LOOP ) + pwm->pwm_ctl[2 ] |=PM8XXX_PWM_LOOP_EN ; + +/* Pause time */ +if (pwm_conf->flags &PM_PWM_LUT_PAUSE_HI_EN ) { +/* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_hi + duty_msec[i] /2 ) + / duty_msec[i]; +for (j =0 ; j <PM8XXX_PWM_PAUSE_COUNT_MAX ; j++) { +if (pause_count[j] >= pause_cnt) +break ; + } + pwm->pwm_ctl[5 ] = (j << +PM8XXX_PWM_PAUSE_COUNT_HI_SHIFT ) & +PM8XXX_PWM_PAUSE_COUNT_HI_MASK ; + pwm->pwm_ctl[5 ] |=PM8XXX_PWM_PAUSE_ENABLE_HIGH ; + }else + pwm->pwm_ctl[5 ] =0 ; + +if (pwm_conf->flags &PM_PWM_LUT_PAUSE_LO_EN ) { +/* Linear search for pause time */ + pause_cnt = (pwm_conf->lut_pause_lo + duty_msec[i] /2 ) + / duty_msec[i]; +for (j =0 ; j <PM8XXX_PWM_PAUSE_COUNT_MAX ; j++) { +if (pause_count[j] >= pause_cnt) +break ; + } + pwm->pwm_ctl[6 ] = (j << +PM8XXX_PWM_PAUSE_COUNT_LO_SHIFT ) & +PM8XXX_PWM_PAUSE_COUNT_LO_MASK ; + pwm->pwm_ctl[6 ] |=PM8XXX_PWM_PAUSE_ENABLE_LOW ; + }else + pwm->pwm_ctl[6 ] =0 ; + + len =7 ; + } + +pm8xxx_pwm_bank_sel (pwm); + +for (i =0 ; i < len; i++) { + rc =pm8xxx_writeb (pwm->chip->dev->parent, +SSBI_REG_ADDR_LPG_CTL (i), + pwm->pwm_ctl[i]); +if (rc) { +pr_err ("pm8xxx_write(): rc=%d (PWM Ctl[%d])\n" , rc, i); +break ; + } + } + +if (ramp_enabled) { + pwm->pwm_ctl[0 ] |= ramp_enabled; + pm8xxx_writeb(pwm->chip->dev->parent,
Quoted 8 lines
drivers/mfd/pm8xxx-pwm.c line 458
Quoted 6 lines
drivers/mfd/pm8xxx-pwm.c new file +6
-0+SSBI_REG_ADDR_LPG_CTL (0 ), + pwm->pwm_ctl[0]); + } + +return rc; +}I would also appreciate if this routine could be made smaller by having it calling sub routines.
I'll add subroutines for this function.
Thank you,
~Willie
Quoted 13 lines
Cheers,
Samuel.--
Intel Open Source Technology Centre
http://oss.intel.com/
--
To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html
Signature · 2 lines
Sent by an employee of the Qualcomm Innovation Center, Inc. The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.