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>
diff --git a/Documentation/devicetree/bindings/pwm/qpnp-pwm.txt b/Documentation/devicetree/bindings/pwm/qpnp-pwm.txt
new file mode 100644
index 0000000..83ce3f8
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/qpnp-pwm.txt
@@ -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.
diff --git a/drivers/platform/msm/qpnp-pwm.c b/drivers/platform/msm/qpnp-pwm.c
index 708d658..6f9af36 100644
--- a/drivers/platform/msm/qpnp-pwm.c
+++ b/drivers/platform/msm/qpnp-pwm.c
@@ -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 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 @@
u16 base_addr;
u16 lut_base_addr;
u16 lut_size;
- bool bypass_lut;
- bool lpg_configured;
};
struct qpnp_pwm_config {
@@ -304,6 +298,8 @@
#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_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 @@
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 @@
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 @@
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_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 @@
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 @@
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 @@
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 @@
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 @@
{
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 @@
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 @@
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 @@
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 @@
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 @@
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,21 +1155,50 @@
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
*
* @pwm: the PWM device
@@ -1356,11 +1325,29 @@
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 @@
}
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 @@
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 @@
/* 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 @@
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 @@
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);
}
diff --git a/include/linux/qpnp/pwm.h b/include/linux/qpnp/pwm.h
index de89a37..50c15e9 100644
--- a/include/linux/qpnp/pwm.h
+++ b/include/linux/qpnp/pwm.h
@@ -114,6 +114,18 @@
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
* @idx_len: number of index
@@ -134,8 +146,6 @@
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