mirror of
https://github.com/followmsi/android_kernel_google_msm.git
synced 2024-11-06 23:17:41 +00:00
platform: msm: qpnp-pwm: Define PWM devicetree bindings
Add the PWM devicetree bindings for the PWM/LPG device present in Qualcomm PM8941 chipset. Also make the necessary changes to the driver to comply with the devicetree binding requirements. Change-Id: I8124e2541028719e5b747bc85ff548ac109a9735 Signed-off-by: Jay Chokshi <jchokshi@codeaurora.org>
This commit is contained in:
parent
0b60f2746c
commit
bef5e61818
3 changed files with 438 additions and 252 deletions
160
Documentation/devicetree/bindings/pwm/qpnp-pwm.txt
Normal file
160
Documentation/devicetree/bindings/pwm/qpnp-pwm.txt
Normal file
|
@ -0,0 +1,160 @@
|
|||
Qualcomm QPNP PWM/LPG controller
|
||||
|
||||
qpnp-pwm driver supports Pulse Width Module (PWM) functionality. PWM feature is
|
||||
used in range of applications such as varying Display brightness, LED dimming,
|
||||
etc. The Qualcomm PMICs have a physical device called Light Pulse Generator
|
||||
(LPG). In addition to support PWM functionality, the LPG module provides
|
||||
a rich set of user defined PWM pattern configurations, such as sawtooth, linear
|
||||
up, linear down, triangular patterns etc. The PWM patterns are used in
|
||||
applications such as charger driver where the driver uses these patterns
|
||||
to indicate various states of charging.
|
||||
|
||||
Required device bindings:
|
||||
- compatible: should be "qcom,qpnp-pwm"
|
||||
- reg: Offset and length of the controller's LPG channel register,
|
||||
and LPG look-up table (LUT). The LPG look-up table is a
|
||||
contiguous address space that is populated with PWM values.
|
||||
The size of PWM value is 9 bit and the size of each
|
||||
entry of the table is 8 bit. Thus, two entries are used
|
||||
to fill each PWM value. The lower entry is used for PWM
|
||||
LSB byte and higher entry is used for PWM MSB bit.
|
||||
- reg-names: Names for the above registers.
|
||||
"qpnp-lpg-channel-base" = physical base address of the
|
||||
controller's LPG channel register.
|
||||
"qpnp-lpg-lut-base" = physical base address of LPG LUT.
|
||||
- qcom,channel-id: channel Id for the PWM.
|
||||
|
||||
Optional device bindings:
|
||||
- qcom,channel-owner: A string value to supply owner information.
|
||||
- qcom,mode-select: 0 = PWM mode
|
||||
1 = LPG mode
|
||||
If this binding is specified along with the required bindings of PWM/LPG then
|
||||
in addition to configure PWM/LPG the qpnp-pwm driver also enables the feature
|
||||
at the probe time. In the case where the binding is not specified the qpnp-pwm
|
||||
driver does not enable the feature. Also, it is considered an error to specify
|
||||
a particular mode using this binding but not the respective feature subnode.
|
||||
|
||||
All PWM devices support both PWM and LPG features within the same device.
|
||||
To support each feature, there are some required and optional bindings passed
|
||||
through device tree.
|
||||
|
||||
The PWM device can enable one feature (either PWM or LPG) at any given time.
|
||||
Therefore, the qpnp-pwm driver applies the last PWM or LPG feature configuration
|
||||
and enables that feature.
|
||||
|
||||
Required bindings to support PWM feature:
|
||||
- qcom,period: PWM period time in microseconds.
|
||||
- qcom,duty: PWM duty time in microseconds.
|
||||
- label: "pwm"
|
||||
|
||||
Required bindings to support LPG feature:
|
||||
The following bindings are needed to configure LPG mode, where a list of
|
||||
duty cycle percentages is populated. The size of the list cannot exceed
|
||||
the size of the LPG look-up table.
|
||||
|
||||
- qcom,period: PWM period time in microseconds.
|
||||
- qcom,duty-percents: List of entries for look-up table
|
||||
- cell-index: Index of look-up table that should be used to start
|
||||
filling up the duty-pct list. start-idx + size of list
|
||||
cannot exceed the size of look-up table.
|
||||
- label: "lpg"
|
||||
|
||||
|
||||
Optional bindings to support LPG feature:
|
||||
- qcom,ramp-step-duration: Time (in ms) to wait before loading next entry of LUT
|
||||
- qcom,lpg-lut-pause-hi: Time (in ms) to wait once pattern reaches to hi
|
||||
index.
|
||||
- qcom,lpg-lut-pause-lo: Time (in ms) to wait once pattern reaches to lo
|
||||
index.
|
||||
- qcom,lpg-lut-ramp-direction: 1 = Start the pattern from lo index to hi index.
|
||||
0 = Start the pattern from hi index to lo index.
|
||||
- qcom,lpg-lut-pattern-repeat: 1 = Repeat the pattern after the pause once it
|
||||
reaches to last duty cycle.
|
||||
0 = Do not repeat the pattern.
|
||||
- qcom,lpg-lut-ramp-toggle: 1 = Toggle the direction of the pattern.
|
||||
0 = Do not toggle the direction.
|
||||
- qcom,lpg-lut-enable-pause-hi: 1 = Enable pause time at hi index.
|
||||
0 = Disable pause time at hi index.
|
||||
- qcom,lpg-lut-enable-pause-lo: 1 = Enable pause time at lo index.
|
||||
0 = Disable pause time at lo index.
|
||||
|
||||
|
||||
Example:
|
||||
qcom,spmi@fc4c0000 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
qcom,pm8941@1 {
|
||||
spmi-slave-container;
|
||||
reg = <0x1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
|
||||
pwm@b100 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "qcom,qpnp-pwm";
|
||||
reg = <0xb100 0x100>,
|
||||
<0xb040 0x80>;
|
||||
reg-names = "qpnp-lpg-channel-base", "qpnp-lpg-lut-base";
|
||||
qcom,channel-id = <0>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
pwm@b200 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "qcom,qpnp-pwm";
|
||||
reg = <0xb200 0x100>,
|
||||
<0xb040 0x80>;
|
||||
reg-names = "qpnp-lpg-channel-base", "qpnp-lpg-lut-base";
|
||||
qcom,channel-id = <1>;
|
||||
qcom,period = <6000000>;
|
||||
status = "okay";
|
||||
qcom,pwm {
|
||||
qcom,duty = <4000000>;
|
||||
label = "pwm";
|
||||
};
|
||||
};
|
||||
|
||||
pwm@b500 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <1>;
|
||||
compatible = "qcom,qpnp-pwm";
|
||||
reg = <0xb500 0x100>,
|
||||
<0xb040 0x80>;
|
||||
reg-names = "qpnp-lpg-channel-base", "qpnp-lpg-lut-base";
|
||||
qcom,channel-id = <4>;
|
||||
qcom,period = <6000000>;
|
||||
qcom,mode-select = <0>;
|
||||
qcom,channel-owner = "RGB-led";
|
||||
status = "okay";
|
||||
|
||||
qcom,pwm {
|
||||
qcom,duty = <4000000>;
|
||||
label = "pwm";
|
||||
};
|
||||
|
||||
qcom,lpg {
|
||||
qcom,duty-percents = <1 14 28 42 56 84 100
|
||||
100 84 56 42 28 14 1>;
|
||||
cell-index = <0>;
|
||||
qcom,ramp-step-duration = <20>;
|
||||
label = "lpg";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
There are couple of ways to configure PWM device channels as shown in above
|
||||
example,
|
||||
1. The PWM device channel #0 is configured with only required device bindings.
|
||||
In this case, the qpnp-pwm driver does not configure any mode by default.
|
||||
|
||||
2. The qpnp-pwm driver configures PWM device channel #1 with PWM feature
|
||||
configuration, but does not enable the channel since "qcom,mode-select" binding
|
||||
is not specified in the devicetree.
|
||||
|
||||
3. Both the PWM and LPG configurations are provided for PWM device channel #4.
|
||||
The qpnp-pwm driver configures both the modes, but enables PWM mode at the probe
|
||||
time. It also sets the channel owner information for the channel.
|
|
@ -1,4 +1,5 @@
|
|||
/* Copyright (c) 2012, Code Aurora Forum. All rights reserved.
|
||||
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 and
|
||||
|
@ -27,6 +28,8 @@
|
|||
#include <linux/qpnp/pwm.h>
|
||||
|
||||
#define QPNP_LPG_DRIVER_NAME "qcom,qpnp-pwm"
|
||||
#define QPNP_LPG_CHANNEL_BASE "qpnp-lpg-channel-base"
|
||||
#define QPNP_LPG_LUT_BASE "qpnp-lpg-lut-base"
|
||||
|
||||
/* LPG Control for LPG_PATTERN_CONFIG */
|
||||
#define QPNP_RAMP_DIRECTION_SHIFT 4
|
||||
|
@ -207,26 +210,19 @@ static unsigned int pt_t[NUM_LPG_PRE_DIVIDE][NUM_CLOCKS] = {
|
|||
|
||||
static RADIX_TREE(lpg_dev_tree, GFP_KERNEL);
|
||||
|
||||
struct qpnp_lut_default_config {
|
||||
u32 *duty_pct_list;
|
||||
int size;
|
||||
int start_idx;
|
||||
};
|
||||
|
||||
struct qpnp_lut_config {
|
||||
struct qpnp_lut_default_config def_config;
|
||||
u8 *duty_pct_list;
|
||||
int list_size;
|
||||
int lo_index;
|
||||
int hi_index;
|
||||
int lut_pause_hi_cnt;
|
||||
int lut_pause_lo_cnt;
|
||||
int ramp_step_ms;
|
||||
bool ramp_direction;
|
||||
bool pattern_repeat;
|
||||
bool ramp_toggle;
|
||||
bool enable_pause_hi;
|
||||
bool enable_pause_lo;
|
||||
u8 *duty_pct_list;
|
||||
int list_len;
|
||||
int lo_index;
|
||||
int hi_index;
|
||||
int lut_pause_hi_cnt;
|
||||
int lut_pause_lo_cnt;
|
||||
int ramp_step_ms;
|
||||
bool ramp_direction;
|
||||
bool pattern_repeat;
|
||||
bool ramp_toggle;
|
||||
bool enable_pause_hi;
|
||||
bool enable_pause_lo;
|
||||
};
|
||||
|
||||
struct qpnp_lpg_config {
|
||||
|
@ -234,8 +230,6 @@ struct qpnp_lpg_config {
|
|||
u16 base_addr;
|
||||
u16 lut_base_addr;
|
||||
u16 lut_size;
|
||||
bool bypass_lut;
|
||||
bool lpg_configured;
|
||||
};
|
||||
|
||||
struct qpnp_pwm_config {
|
||||
|
@ -304,6 +298,8 @@ static inline void qpnp_set_control(u8 *val, bool pwm_hi, bool pwm_lo,
|
|||
|
||||
#define QPNP_ENABLE_LUT_CONTROL(p_val) qpnp_set_control(p_val, 1, 1, 1, 0, 1)
|
||||
#define QPNP_ENABLE_PWM_CONTROL(p_val) qpnp_set_control(p_val, 1, 1, 0, 1, 0)
|
||||
#define QPNP_IS_PWM_CONFIG_SELECTED(val) (val & QPNP_PWM_SRC_SELECT_MASK)
|
||||
|
||||
|
||||
static inline void qpnp_convert_to_lut_flags(int *flags,
|
||||
struct qpnp_lut_config *l_config)
|
||||
|
@ -316,10 +312,10 @@ static inline void qpnp_convert_to_lut_flags(int *flags,
|
|||
}
|
||||
|
||||
static inline void qpnp_set_lut_params(struct lut_params *l_params,
|
||||
struct qpnp_lut_config *l_config)
|
||||
struct qpnp_lut_config *l_config, int s_idx, int size)
|
||||
{
|
||||
l_params->start_idx = l_config->def_config.start_idx;
|
||||
l_params->idx_len = l_config->def_config.size;
|
||||
l_params->start_idx = s_idx;
|
||||
l_params->idx_len = size;
|
||||
l_params->lut_pause_hi = l_config->lut_pause_hi_cnt;
|
||||
l_params->lut_pause_lo = l_config->lut_pause_lo_cnt;
|
||||
l_params->ramp_step_ms = l_config->ramp_step_ms;
|
||||
|
@ -442,7 +438,7 @@ static int qpnp_lpg_change_table(struct pwm_device *pwm,
|
|||
struct qpnp_lut_config *lut = &chip->lpg_config.lut_config;
|
||||
int i, pwm_size, rc = 0;
|
||||
int burst_size = SPMI_MAX_BUF_LEN;
|
||||
int list_len = lut->list_size << 1;
|
||||
int list_len = lut->list_len << 1;
|
||||
int offset = lut->lo_index << 2;
|
||||
|
||||
pwm_size = QPNP_GET_PWM_SIZE(
|
||||
|
@ -451,15 +447,15 @@ static int qpnp_lpg_change_table(struct pwm_device *pwm,
|
|||
|
||||
max_pwm_value = (1 << pwm_size) - 1;
|
||||
|
||||
if (unlikely(lut->list_size != (lut->hi_index - lut->lo_index + 1))) {
|
||||
if (unlikely(lut->list_len != (lut->hi_index - lut->lo_index + 1))) {
|
||||
pr_err("LUT internal Data structure corruption detected\n");
|
||||
pr_err("LUT list size: %d\n", lut->list_size);
|
||||
pr_err("LUT list size: %d\n", lut->list_len);
|
||||
pr_err("However, index size is: %d\n",
|
||||
(lut->hi_index - lut->lo_index + 1));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i <= lut->list_size; i++) {
|
||||
for (i = 0; i <= lut->list_len; i++) {
|
||||
if (raw_value)
|
||||
pwm_value = duty_pct[i];
|
||||
else
|
||||
|
@ -597,7 +593,7 @@ static int qpnp_lpg_configure_pwm(struct pwm_device *pwm)
|
|||
lpg_config->base_addr, QPNP_LPG_PWM_TYPE_CONFIG, 1, chip);
|
||||
}
|
||||
|
||||
static int qpnp_pwm_configure_control(struct pwm_device *pwm)
|
||||
static int qpnp_configure_pwm_control(struct pwm_device *pwm)
|
||||
{
|
||||
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||||
struct qpnp_lpg_chip *chip = pwm->chip;
|
||||
|
@ -615,7 +611,7 @@ static int qpnp_pwm_configure_control(struct pwm_device *pwm)
|
|||
|
||||
}
|
||||
|
||||
static int qpnp_lpg_configure_control(struct pwm_device *pwm)
|
||||
static int qpnp_configure_lpg_control(struct pwm_device *pwm)
|
||||
{
|
||||
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||||
struct qpnp_lpg_chip *chip = pwm->chip;
|
||||
|
@ -789,7 +785,7 @@ static int qpnp_lpg_change_lut(struct pwm_device *pwm)
|
|||
pr_err("Failed to configure LUT pattern");
|
||||
return rc;
|
||||
}
|
||||
rc = qpnp_lpg_configure_control(pwm);
|
||||
rc = qpnp_configure_lpg_control(pwm);
|
||||
if (rc) {
|
||||
pr_err("Failed to configure pause registers");
|
||||
return rc;
|
||||
|
@ -829,7 +825,7 @@ static int qpnp_lpg_enable_lut(struct pwm_device *pwm)
|
|||
lpg_config->base_addr, QPNP_RAMP_CONTROL, 1, chip);
|
||||
}
|
||||
|
||||
static int qpnp_lpg_disable_lut(struct pwm_device *pwm)
|
||||
static int qpnp_disable_lut(struct pwm_device *pwm)
|
||||
{
|
||||
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||||
struct qpnp_lpg_chip *chip = pwm->chip;
|
||||
|
@ -863,7 +859,7 @@ static int qpnp_lpg_enable_pwm(struct pwm_device *pwm)
|
|||
lpg_config->base_addr, QPNP_RAMP_CONTROL, 1, chip);
|
||||
}
|
||||
|
||||
static int qpnp_lpg_disable_pwm(struct pwm_device *pwm)
|
||||
static int qpnp_disable_pwm(struct pwm_device *pwm)
|
||||
{
|
||||
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||||
struct qpnp_lpg_chip *chip = pwm->chip;
|
||||
|
@ -914,15 +910,13 @@ static int _pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
|
|||
return rc;
|
||||
}
|
||||
|
||||
rc = qpnp_pwm_configure_control(pwm);
|
||||
rc = qpnp_configure_pwm_control(pwm);
|
||||
if (rc) {
|
||||
pr_err("Could not update PWM control for");
|
||||
pr_err("channel %d rc=%d\n", pwm_config->channel_id, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
pwm->chip->lpg_config.lpg_configured = 1;
|
||||
|
||||
pr_debug("duty/period=%u/%u usec: pwm_value=%d (of %d)\n",
|
||||
(unsigned)duty_us, (unsigned)period_us,
|
||||
pwm_config->pwm_value, 1 << period->pwm_size);
|
||||
|
@ -935,8 +929,6 @@ static int _pwm_lut_config(struct pwm_device *pwm, int period_us,
|
|||
{
|
||||
struct qpnp_lpg_config *lpg_config;
|
||||
struct qpnp_lut_config *lut_config;
|
||||
struct qpnp_lut_default_config *def_lut_config =
|
||||
&lut_config->def_config;
|
||||
struct pwm_period_config *period;
|
||||
struct qpnp_pwm_config *pwm_config;
|
||||
int start_idx = lut_params.start_idx;
|
||||
|
@ -948,23 +940,6 @@ static int _pwm_lut_config(struct pwm_device *pwm, int period_us,
|
|||
pwm_config = &pwm->pwm_config;
|
||||
lpg_config = &pwm->chip->lpg_config;
|
||||
lut_config = &lpg_config->lut_config;
|
||||
def_lut_config = &lut_config->def_config;
|
||||
|
||||
if ((start_idx + len) > lpg_config->lut_size) {
|
||||
pr_err("Exceed LUT limit\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
if ((unsigned)period_us > PM_PWM_PERIOD_MAX ||
|
||||
(unsigned)period_us < PM_PWM_PERIOD_MIN) {
|
||||
pr_err("Period out of range\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pwm_config->in_use) {
|
||||
pr_err("channel_id: %d: stale handle?\n",
|
||||
pwm_config->channel_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
period = &pwm_config->period;
|
||||
|
||||
|
@ -981,37 +956,10 @@ static int _pwm_lut_config(struct pwm_device *pwm, int period_us,
|
|||
if (flags & PM_PWM_LUT_USE_RAW_VALUE)
|
||||
raw_lut = 1;
|
||||
|
||||
lut_config->list_size = len;
|
||||
lut_config->list_len = len;
|
||||
lut_config->lo_index = start_idx;
|
||||
lut_config->hi_index = start_idx + len - 1;
|
||||
|
||||
/*
|
||||
* LUT may not be specified in device tree by default.
|
||||
* This is the first time user is configuring it.
|
||||
*/
|
||||
if (lpg_config->bypass_lut) {
|
||||
def_lut_config->duty_pct_list = kzalloc(sizeof(u32) *
|
||||
len, GFP_KERNEL);
|
||||
if (!def_lut_config->duty_pct_list) {
|
||||
pr_err("kzalloc failed on def_duty_pct_list\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
lut_config->duty_pct_list = kzalloc(lpg_config->lut_size *
|
||||
sizeof(u16), GFP_KERNEL);
|
||||
if (!lut_config->duty_pct_list) {
|
||||
pr_err("kzalloc failed on duty_pct_list\n");
|
||||
kfree(def_lut_config->duty_pct_list);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
def_lut_config->size = len;
|
||||
def_lut_config->start_idx = start_idx;
|
||||
memcpy(def_lut_config->duty_pct_list, duty_pct, len);
|
||||
|
||||
lpg_config->bypass_lut = 0;
|
||||
}
|
||||
|
||||
rc = qpnp_lpg_change_table(pwm, duty_pct, raw_lut);
|
||||
if (rc) {
|
||||
pr_err("qpnp_lpg_change_table: rc=%d\n", rc);
|
||||
|
@ -1041,12 +989,28 @@ after_table_write:
|
|||
lut_config->ramp_toggle = !!(flags & PM_PWM_LUT_REVERSE);
|
||||
lut_config->enable_pause_hi = !!(flags & PM_PWM_LUT_PAUSE_HI_EN);
|
||||
lut_config->enable_pause_lo = !!(flags & PM_PWM_LUT_PAUSE_LO_EN);
|
||||
lpg_config->bypass_lut = 0;
|
||||
|
||||
rc = qpnp_lpg_change_lut(pwm);
|
||||
|
||||
if (!rc)
|
||||
lpg_config->lpg_configured = 1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int _pwm_enable(struct pwm_device *pwm)
|
||||
{
|
||||
int rc;
|
||||
struct qpnp_lpg_chip *chip;
|
||||
|
||||
chip = pwm->chip;
|
||||
|
||||
mutex_lock(&pwm->chip->lpg_mutex);
|
||||
|
||||
if (QPNP_IS_PWM_CONFIG_SELECTED(
|
||||
chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]))
|
||||
rc = qpnp_lpg_enable_pwm(pwm);
|
||||
else
|
||||
rc = qpnp_lpg_enable_lut(pwm);
|
||||
|
||||
mutex_unlock(&pwm->chip->lpg_mutex);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
@ -1108,11 +1072,10 @@ void pwm_free(struct pwm_device *pwm)
|
|||
pwm_config = &pwm->pwm_config;
|
||||
|
||||
if (pwm_config->in_use) {
|
||||
qpnp_lpg_disable_pwm(pwm);
|
||||
qpnp_lpg_disable_lut(pwm);
|
||||
qpnp_disable_pwm(pwm);
|
||||
qpnp_disable_lut(pwm);
|
||||
pwm_config->in_use = 0;
|
||||
pwm_config->lable = NULL;
|
||||
pwm->chip->lpg_config.lpg_configured = 0;
|
||||
}
|
||||
|
||||
mutex_unlock(&pwm->chip->lpg_mutex);
|
||||
|
@ -1155,43 +1118,20 @@ EXPORT_SYMBOL_GPL(pwm_config);
|
|||
int pwm_enable(struct pwm_device *pwm)
|
||||
{
|
||||
struct qpnp_pwm_config *p_config;
|
||||
struct qpnp_lpg_chip *chip;
|
||||
int rc = 0;
|
||||
|
||||
if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
|
||||
pr_err("Invalid pwm handle or no pwm_chip\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&pwm->chip->lpg_mutex);
|
||||
|
||||
chip = pwm->chip;
|
||||
p_config = &pwm->pwm_config;
|
||||
|
||||
if (!p_config->in_use) {
|
||||
pr_err("channel_id: %d: stale handle?\n", p_config->channel_id);
|
||||
rc = -EINVAL;
|
||||
goto out_unlock;
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!pwm->chip->lpg_config.lpg_configured) {
|
||||
pr_err("Request received to enable PWM for channel Id: %d\n",
|
||||
p_config->channel_id);
|
||||
pr_err("However, PWM isn't configured\n");
|
||||
pr_err("falling back to defaultconfiguration\n");
|
||||
rc = _pwm_config(pwm, p_config->pwm_duty,
|
||||
p_config->pwm_period);
|
||||
if (rc) {
|
||||
pr_err("Could not apply default PWM config\n");
|
||||
goto out_unlock;
|
||||
}
|
||||
}
|
||||
|
||||
rc = qpnp_lpg_enable_pwm(pwm);
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&pwm->chip->lpg_mutex);
|
||||
return rc;
|
||||
return _pwm_enable(pwm);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_enable);
|
||||
|
||||
|
@ -1215,20 +1155,49 @@ void pwm_disable(struct pwm_device *pwm)
|
|||
pwm_config = &pwm->pwm_config;
|
||||
|
||||
if (pwm_config->in_use) {
|
||||
if (!pwm->chip->lpg_config.lpg_configured) {
|
||||
pr_err("Request received to disable PWM for\n");
|
||||
pr_err("channel Id: %d\n", pwm_config->channel_id);
|
||||
pr_err("However PWM is not configured by any means\n");
|
||||
goto out_unlock;
|
||||
}
|
||||
qpnp_lpg_disable_pwm(pwm);
|
||||
if (QPNP_IS_PWM_CONFIG_SELECTED(
|
||||
chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]))
|
||||
qpnp_disable_pwm(pwm);
|
||||
else
|
||||
qpnp_disable_lut(pwm);
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&pwm->chip->lpg_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_disable);
|
||||
|
||||
/**
|
||||
* pwm_change_mode - Change the PWM mode configuration
|
||||
* @pwm: the PWM device
|
||||
* @mode: Mode selection value
|
||||
*/
|
||||
int pwm_change_mode(struct pwm_device *pwm, enum pm_pwm_mode mode)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
|
||||
pr_err("Invalid pwm handle or no pwm_chip\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mode < PM_PWM_MODE_PWM || mode > PM_PWM_MODE_LPG) {
|
||||
pr_err("Invalid mode value\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&pwm->chip->lpg_mutex);
|
||||
|
||||
if (mode)
|
||||
rc = qpnp_configure_lpg_control(pwm);
|
||||
else
|
||||
rc = qpnp_configure_pwm_control(pwm);
|
||||
|
||||
mutex_unlock(&pwm->chip->lpg_mutex);
|
||||
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_change_mode);
|
||||
|
||||
/**
|
||||
* pwm_config_period - change PWM period
|
||||
*
|
||||
|
@ -1356,11 +1325,29 @@ int pwm_lut_config(struct pwm_device *pwm, int period_us,
|
|||
if (pwm->chip == NULL)
|
||||
return -ENODEV;
|
||||
|
||||
if (!pwm->pwm_config.in_use) {
|
||||
pr_err("channel_id: %d: stale handle?\n",
|
||||
pwm->pwm_config.channel_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (duty_pct == NULL && !(lut_params.flags & PM_PWM_LUT_NO_TABLE)) {
|
||||
pr_err("Invalid duty_pct with flag\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((lut_params.start_idx + lut_params.idx_len) >
|
||||
pwm->chip->lpg_config.lut_size) {
|
||||
pr_err("Exceed LUT limit\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((unsigned)period_us > PM_PWM_PERIOD_MAX ||
|
||||
(unsigned)period_us < PM_PWM_PERIOD_MIN) {
|
||||
pr_err("Period out of range\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&pwm->chip->lpg_mutex);
|
||||
|
||||
rc = _pwm_lut_config(pwm, period_us, duty_pct, lut_params);
|
||||
|
@ -1371,87 +1358,136 @@ int pwm_lut_config(struct pwm_device *pwm, int period_us,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_lut_config);
|
||||
|
||||
/**
|
||||
* pwm_lut_enable - control a PWM device to start/stop LUT ramp
|
||||
* @pwm: the PWM device
|
||||
* @start: to start (1), or stop (0)
|
||||
*/
|
||||
int pwm_lut_enable(struct pwm_device *pwm, int start)
|
||||
static int qpnp_parse_pwm_dt_config(struct device_node *of_pwm_node,
|
||||
struct device_node *of_parent, struct qpnp_lpg_chip *chip)
|
||||
{
|
||||
struct qpnp_lpg_config *lpg_config;
|
||||
struct qpnp_pwm_config *p_config;
|
||||
struct lut_params lut_params;
|
||||
int rc = 0;
|
||||
int rc, period;
|
||||
struct pwm_device *pwm_dev = &chip->pwm_dev;
|
||||
|
||||
if (pwm == NULL || IS_ERR(pwm)) {
|
||||
pr_err("Invalid pwm handle\n");
|
||||
rc = of_property_read_u32(of_parent, "qcom,period", (u32 *)&period);
|
||||
if (rc) {
|
||||
pr_err("node is missing PWM Period prop");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(of_pwm_node, "qcom,duty",
|
||||
&pwm_dev->pwm_config.pwm_duty);
|
||||
if (rc) {
|
||||
pr_err("node is missing PWM Duty prop");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = _pwm_config(pwm_dev, pwm_dev->pwm_config.pwm_duty, period);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
#define qpnp_check_optional_dt_bindings(func) \
|
||||
do { \
|
||||
rc = func; \
|
||||
if (rc && rc != -EINVAL) \
|
||||
goto out; \
|
||||
rc = 0; \
|
||||
} while (0);
|
||||
|
||||
static int qpnp_parse_lpg_dt_config(struct device_node *of_lpg_node,
|
||||
struct device_node *of_parent, struct qpnp_lpg_chip *chip)
|
||||
{
|
||||
int rc, period, list_size, start_idx, *duty_pct_list;
|
||||
struct pwm_device *pwm_dev = &chip->pwm_dev;
|
||||
struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
|
||||
struct qpnp_lut_config *lut_config = &lpg_config->lut_config;
|
||||
struct lut_params lut_params;
|
||||
|
||||
rc = of_property_read_u32(of_parent, "qcom,period", &period);
|
||||
if (rc) {
|
||||
pr_err("node is missing PWM Period prop");
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (!of_get_property(of_lpg_node, "qcom,duty-percents", &list_size)) {
|
||||
pr_err("node is missing duty-pct list");
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(of_lpg_node, "cell-index", &start_idx);
|
||||
if (rc) {
|
||||
pr_err("Missing start index");
|
||||
return rc;
|
||||
}
|
||||
|
||||
list_size /= sizeof(u32);
|
||||
|
||||
if (list_size + start_idx > lpg_config->lut_size) {
|
||||
pr_err("duty pct list size overflows\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (pwm->chip == NULL)
|
||||
return -ENODEV;
|
||||
duty_pct_list = kzalloc(sizeof(u32) * list_size, GFP_KERNEL);
|
||||
|
||||
lpg_config = &pwm->chip->lpg_config;
|
||||
p_config = &pwm->pwm_config;
|
||||
|
||||
mutex_lock(&pwm->chip->lpg_mutex);
|
||||
|
||||
if (start) {
|
||||
if (!lpg_config->lpg_configured) {
|
||||
pr_err("Request received to enable LUT for\n");
|
||||
pr_err("LPG channel %d\n", pwm->pwm_config.channel_id);
|
||||
pr_err("But LPG is not configured, falling back to\n");
|
||||
pr_err(" default LUT configuration if available\n");
|
||||
|
||||
if (lpg_config->bypass_lut) {
|
||||
pr_err("No default LUT configuration found\n");
|
||||
pr_err("Use pwm_lut_config() to configure\n");
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
qpnp_set_lut_params(&lut_params,
|
||||
&lpg_config->lut_config);
|
||||
|
||||
rc = _pwm_lut_config(pwm, p_config->pwm_period,
|
||||
(int *)lpg_config->lut_config.def_config.duty_pct_list,
|
||||
lut_params);
|
||||
if (rc) {
|
||||
pr_err("Could not set the default LUT conf\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
rc = qpnp_lpg_enable_lut(pwm);
|
||||
} else {
|
||||
if (unlikely(!lpg_config->lpg_configured)) {
|
||||
pr_err("LPG isn't configured\n");
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
rc = qpnp_lpg_disable_lut(pwm);
|
||||
if (!duty_pct_list) {
|
||||
pr_err("kzalloc failed on duty_pct_list\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32_array(of_lpg_node, "qcom,duty-percents",
|
||||
duty_pct_list, list_size);
|
||||
if (rc) {
|
||||
pr_err("invalid or missing property:\n");
|
||||
pr_err("qcom,duty-pcts-list\n");
|
||||
kfree(duty_pct_list);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Read optional properties */
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,ramp-step-duration", &lut_config->ramp_step_ms));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-pause-hi", &lut_config->lut_pause_hi_cnt));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-pause-lo", &lut_config->lut_pause_lo_cnt));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-ramp-direction",
|
||||
(u32 *)&lut_config->ramp_direction));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-pattern-repeat",
|
||||
(u32 *)&lut_config->pattern_repeat));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-ramp-toggle",
|
||||
(u32 *)&lut_config->ramp_toggle));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-enable-pause-hi",
|
||||
(u32 *)&lut_config->enable_pause_hi));
|
||||
qpnp_check_optional_dt_bindings(of_property_read_u32(of_lpg_node,
|
||||
"qcom,lpg-lut-enable-pause-lo",
|
||||
(u32 *)&lut_config->enable_pause_lo));
|
||||
|
||||
qpnp_set_lut_params(&lut_params, lut_config, start_idx, list_size);
|
||||
|
||||
_pwm_lut_config(pwm_dev, period, duty_pct_list, lut_params);
|
||||
|
||||
out:
|
||||
mutex_unlock(&pwm->chip->lpg_mutex);
|
||||
kfree(duty_pct_list);
|
||||
return rc;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_lut_enable);
|
||||
|
||||
/* Fill in lpg device elements based on values found in device tree. */
|
||||
static int qpnp_lpg_get_dt_config(struct spmi_device *spmi,
|
||||
static int qpnp_parse_dt_config(struct spmi_device *spmi,
|
||||
struct qpnp_lpg_chip *chip)
|
||||
{
|
||||
int rc;
|
||||
int rc, enable;
|
||||
const char *lable;
|
||||
struct resource *res;
|
||||
struct device_node *node;
|
||||
int found_pwm_subnode = 0;
|
||||
int found_lpg_subnode = 0;
|
||||
struct device_node *of_node = spmi->dev.of_node;
|
||||
struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
|
||||
struct pwm_device *pwm_dev = &chip->pwm_dev;
|
||||
struct qpnp_lut_config *lut_config = &chip->lpg_config.lut_config;
|
||||
struct qpnp_lut_default_config *def_lut_config =
|
||||
&lut_config->def_config;
|
||||
struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
|
||||
struct qpnp_lut_config *lut_config = &lpg_config->lut_config;
|
||||
|
||||
res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 0);
|
||||
res = spmi_get_resource_byname(spmi, NULL, IORESOURCE_MEM,
|
||||
QPNP_LPG_CHANNEL_BASE);
|
||||
if (!res) {
|
||||
dev_err(&spmi->dev, "%s: node is missing base address\n",
|
||||
__func__);
|
||||
|
@ -1460,7 +1496,8 @@ static int qpnp_lpg_get_dt_config(struct spmi_device *spmi,
|
|||
|
||||
lpg_config->base_addr = res->start;
|
||||
|
||||
res = spmi_get_resource(spmi, 0, IORESOURCE_MEM, 1);
|
||||
res = spmi_get_resource_byname(spmi, NULL, IORESOURCE_MEM,
|
||||
QPNP_LPG_LUT_BASE);
|
||||
if (!res) {
|
||||
dev_err(&spmi->dev, "%s: node is missing LUT base address\n",
|
||||
__func__);
|
||||
|
@ -1471,88 +1508,68 @@ static int qpnp_lpg_get_dt_config(struct spmi_device *spmi,
|
|||
/* Each entry of LUT is of 2 bytes */
|
||||
lpg_config->lut_size = resource_size(res) >> 1;
|
||||
|
||||
lut_config->duty_pct_list = kzalloc(lpg_config->lut_size *
|
||||
sizeof(u16), GFP_KERNEL);
|
||||
if (!lut_config->duty_pct_list) {
|
||||
pr_err("can not allocate duty pct list\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(of_node, "qcom,channel-id",
|
||||
&pwm_dev->pwm_config.channel_id);
|
||||
if (rc) {
|
||||
dev_err(&spmi->dev, "%s: node is missing LPG channel id",
|
||||
dev_err(&spmi->dev, "%s: node is missing LPG channel id\n",
|
||||
__func__);
|
||||
return rc;
|
||||
goto out;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32(of_node, "qcom,period",
|
||||
&pwm_dev->pwm_config.pwm_period);
|
||||
if (rc) {
|
||||
dev_err(&spmi->dev, "%s: node is missing PWM Period value",
|
||||
for_each_child_of_node(of_node, node) {
|
||||
rc = of_property_read_string(node, "label", &lable);
|
||||
if (rc) {
|
||||
dev_err(&spmi->dev, "%s: Missing lable property\n",
|
||||
__func__);
|
||||
return rc;
|
||||
goto out;
|
||||
}
|
||||
if (!strncmp(lable, "pwm", 3)) {
|
||||
rc = qpnp_parse_pwm_dt_config(node, of_node, chip);
|
||||
if (rc)
|
||||
goto out;
|
||||
found_pwm_subnode = 1;
|
||||
} else if (!strncmp(lable, "lpg", 3)) {
|
||||
qpnp_parse_lpg_dt_config(node, of_node, chip);
|
||||
if (rc)
|
||||
goto out;
|
||||
found_lpg_subnode = 1;
|
||||
} else {
|
||||
dev_err(&spmi->dev, "%s: Invalid value for lable prop",
|
||||
__func__);
|
||||
}
|
||||
}
|
||||
|
||||
if (!of_get_property(of_node, "qcom,duty-percents",
|
||||
&def_lut_config->size)) {
|
||||
lpg_config->bypass_lut = 1;
|
||||
}
|
||||
|
||||
if (lpg_config->bypass_lut)
|
||||
rc = of_property_read_u32(of_node, "qcom,mode-select", &enable);
|
||||
if (rc)
|
||||
goto read_opt_props;
|
||||
|
||||
rc = of_property_read_u32(of_node, "qcom,start-index",
|
||||
&def_lut_config->start_idx);
|
||||
|
||||
if (rc) {
|
||||
dev_err(&spmi->dev, "Missing start index");
|
||||
return rc;
|
||||
if ((enable == PM_PWM_MODE_PWM && found_pwm_subnode == 0) ||
|
||||
(enable == PM_PWM_MODE_LPG && found_lpg_subnode == 0)) {
|
||||
dev_err(&spmi->dev, "%s: Invalid mode select\n", __func__);
|
||||
rc = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
def_lut_config->size /= sizeof(u32);
|
||||
|
||||
def_lut_config->duty_pct_list = kzalloc(sizeof(u32) *
|
||||
def_lut_config->size, GFP_KERNEL);
|
||||
if (!def_lut_config->duty_pct_list) {
|
||||
dev_err(&spmi->dev, "%s: kzalloc failed on duty_pct_list\n",
|
||||
__func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
rc = of_property_read_u32_array(of_node, "qcom,duty-percents",
|
||||
def_lut_config->duty_pct_list, def_lut_config->size);
|
||||
if (rc) {
|
||||
dev_err(&spmi->dev, "invalid or missing property:\n");
|
||||
dev_err(&spmi->dev, "qcom,duty-pcts-list\n");
|
||||
kfree(def_lut_config->duty_pct_list);
|
||||
return rc;
|
||||
}
|
||||
|
||||
lut_config->duty_pct_list = kzalloc(lpg_config->lut_size * sizeof(u16),
|
||||
GFP_KERNEL);
|
||||
if (!lut_config->duty_pct_list) {
|
||||
dev_err(&spmi->dev, "can not allocate duty pct list\n");
|
||||
kfree(def_lut_config->duty_pct_list);
|
||||
return -ENOMEM;
|
||||
}
|
||||
pwm_change_mode(pwm_dev, enable);
|
||||
_pwm_enable(pwm_dev);
|
||||
|
||||
read_opt_props:
|
||||
/* Initialize optional config parameters from DT if provided */
|
||||
of_property_read_u32(of_node, "qcom,duty",
|
||||
&pwm_dev->pwm_config.pwm_duty);
|
||||
of_property_read_u32(of_node, "qcom,ramp-step-duration",
|
||||
&lut_config->ramp_step_ms);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-pause-hi",
|
||||
&lut_config->lut_pause_hi_cnt);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-pause-lo",
|
||||
&lut_config->lut_pause_lo_cnt);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-ramp-direction",
|
||||
(u32 *)&lut_config->ramp_direction);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-pattern-repeat",
|
||||
(u32 *)&lut_config->pattern_repeat);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-ramp-toggle",
|
||||
(u32 *)&lut_config->ramp_toggle);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-enable-pause-hi",
|
||||
(u32 *)&lut_config->enable_pause_hi);
|
||||
of_property_read_u32(of_node, "qcom,lpg-lut-enable-pause-lo",
|
||||
(u32 *)&lut_config->enable_pause_lo);
|
||||
of_property_read_string(node, "qcom,channel-owner",
|
||||
&pwm_dev->pwm_config.lable);
|
||||
|
||||
return 0;
|
||||
|
||||
out:
|
||||
kfree(lut_config->duty_pct_list);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int __devinit qpnp_pwm_probe(struct spmi_device *spmi)
|
||||
|
@ -1572,7 +1589,7 @@ static int __devinit qpnp_pwm_probe(struct spmi_device *spmi)
|
|||
chip->pwm_dev.chip = chip;
|
||||
dev_set_drvdata(&spmi->dev, chip);
|
||||
|
||||
rc = qpnp_lpg_get_dt_config(spmi, chip);
|
||||
rc = qpnp_parse_dt_config(spmi, chip);
|
||||
|
||||
if (rc)
|
||||
goto failed_config;
|
||||
|
@ -1610,7 +1627,6 @@ static int __devexit qpnp_pwm_remove(struct spmi_device *spmi)
|
|||
if (chip) {
|
||||
lpg_config = &chip->lpg_config;
|
||||
kfree(lpg_config->lut_config.duty_pct_list);
|
||||
kfree(lpg_config->lut_config.def_config.duty_pct_list);
|
||||
mutex_destroy(&chip->lpg_mutex);
|
||||
kfree(chip);
|
||||
}
|
||||
|
|
|
@ -113,6 +113,18 @@ int pwm_config_period(struct pwm_device *pwm,
|
|||
|
||||
int pwm_config_pwm_value(struct pwm_device *pwm, int pwm_value);
|
||||
|
||||
/*
|
||||
* enum pm_pwm_mode - PWM mode selection
|
||||
* %PM_PWM_MODE_PWM - Select PWM mode
|
||||
* %PM_PWM_MODE_LPG - Select LPG mode
|
||||
*/
|
||||
enum pm_pwm_mode {
|
||||
PM_PWM_MODE_PWM,
|
||||
PM_PWM_MODE_LPG,
|
||||
};
|
||||
|
||||
int pwm_change_mode(struct pwm_device *pwm, enum pm_pwm_mode mode);
|
||||
|
||||
/*
|
||||
* lut_params: Lookup table (LUT) parameters
|
||||
* @start_idx: start index in lookup table from 0 to MAX-1
|
||||
|
@ -134,8 +146,6 @@ struct lut_params {
|
|||
int pwm_lut_config(struct pwm_device *pwm, int period_us,
|
||||
int duty_pct[], struct lut_params lut_params);
|
||||
|
||||
int pwm_lut_enable(struct pwm_device *pwm, int start);
|
||||
|
||||
/* Standard APIs supported */
|
||||
/*
|
||||
* pwm_request - request a PWM device
|
||||
|
|
Loading…
Reference in a new issue