1802 lines
47 KiB
C
1802 lines
47 KiB
C
|
/* Copyright (c) 2012-2013, The Linux Foundation. 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
|
||
|
* only version 2 as published by the Free Software Foundation.
|
||
|
*
|
||
|
* 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.
|
||
|
*/
|
||
|
/*
|
||
|
* Qualcomm QPNP Pulse Width Modulation (PWM) driver
|
||
|
*
|
||
|
* The HW module is also called LPG (Light Pattern Generator).
|
||
|
*/
|
||
|
|
||
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
||
|
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/err.h>
|
||
|
#include <linux/spmi.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/radix-tree.h>
|
||
|
#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
|
||
|
#define QPNP_RAMP_DIRECTION_MASK 0x10
|
||
|
#define QPNP_PATTERN_REPEAT_SHIFT 3
|
||
|
#define QPNP_PATTERN_REPEAT_MASK 0x08
|
||
|
#define QPNP_RAMP_TOGGLE_SHIFT 2
|
||
|
#define QPNP_RAMP_TOGGLE_MASK 0x04
|
||
|
#define QPNP_EN_PAUSE_HI_SHIFT 1
|
||
|
#define QPNP_EN_PAUSE_HI_MASK 0x02
|
||
|
#define QPNP_EN_PAUSE_LO_MASK 0x01
|
||
|
|
||
|
/* LPG Control for LPG_PWM_SIZE_CLK */
|
||
|
#define QPNP_PWM_SIZE_SHIFT 4
|
||
|
#define QPNP_PWM_SIZE_MASK 0x30
|
||
|
#define QPNP_PWM_FREQ_CLK_SELECT_SHIFT 0
|
||
|
#define QPNP_PWM_FREQ_CLK_SELECT_MASK 0x03
|
||
|
#define QPNP_PWM_SIZE_9_BIT 0x03
|
||
|
|
||
|
#define QPNP_SET_PWM_CLK(val, clk, pwm_size) \
|
||
|
do { \
|
||
|
val = (clk + 1) & QPNP_PWM_FREQ_CLK_SELECT_MASK; \
|
||
|
val |= ((pwm_size > 6 ? QPNP_PWM_SIZE_9_BIT : 0) << \
|
||
|
QPNP_PWM_SIZE_SHIFT) & QPNP_PWM_SIZE_MASK; \
|
||
|
} while (0)
|
||
|
|
||
|
#define QPNP_GET_PWM_SIZE(reg) ((reg & QPNP_PWM_SIZE_MASK) \
|
||
|
>> QPNP_PWM_SIZE_SHIFT)
|
||
|
|
||
|
/* LPG Control for LPG_PWM_FREQ_PREDIV_CLK */
|
||
|
#define QPNP_PWM_FREQ_PRE_DIVIDE_SHIFT 5
|
||
|
#define QPNP_PWM_FREQ_PRE_DIVIDE_MASK 0x60
|
||
|
#define QPNP_PWM_FREQ_EXP_MASK 0x07
|
||
|
|
||
|
#define QPNP_SET_PWM_FREQ_PREDIV(val, pre_div, pre_div_exp) \
|
||
|
do { \
|
||
|
val = (pre_div << QPNP_PWM_FREQ_PRE_DIVIDE_SHIFT) & \
|
||
|
QPNP_PWM_FREQ_PRE_DIVIDE_MASK; \
|
||
|
val |= pre_div_exp & QPNP_PWM_FREQ_EXP_MASK; \
|
||
|
} while (0)
|
||
|
|
||
|
/* LPG Control for LPG_PWM_TYPE_CONFIG */
|
||
|
#define QPNP_EN_GLITCH_REMOVAL_SHIFT 5
|
||
|
#define QPNP_EN_GLITCH_REMOVAL_MASK 0x20
|
||
|
#define QPNP_EN_FULL_SCALE_SHIFT 3
|
||
|
#define QPNP_EN_FULL_SCALE_MASK 0x08
|
||
|
#define QPNP_EN_PHASE_STAGGER_SHIFT 2
|
||
|
#define QPNP_EN_PHASE_STAGGER_MASK 0x04
|
||
|
#define QPNP_PHASE_STAGGER_MASK 0x03
|
||
|
|
||
|
/* LPG Control for PWM_VALUE_LSB */
|
||
|
#define QPNP_PWM_VALUE_LSB_MASK 0xFF
|
||
|
|
||
|
/* LPG Control for PWM_VALUE_MSB */
|
||
|
#define QPNP_PWM_VALUE_MSB_SHIFT 8
|
||
|
#define QPNP_PWM_VALUE_MSB_MASK 0x01
|
||
|
|
||
|
/* LPG Control for ENABLE_CONTROL */
|
||
|
#define QPNP_EN_PWM_HIGH_SHIFT 7
|
||
|
#define QPNP_EN_PWM_HIGH_MASK 0x80
|
||
|
#define QPNP_EN_PWM_LO_SHIFT 6
|
||
|
#define QPNP_EN_PWM_LO_MASK 0x40
|
||
|
#define QPNP_EN_PWM_OUTPUT_SHIFT 5
|
||
|
#define QPNP_EN_PWM_OUTPUT_MASK 0x20
|
||
|
#define QPNP_PWM_SRC_SELECT_SHIFT 2
|
||
|
#define QPNP_PWM_SRC_SELECT_MASK 0x04
|
||
|
#define QPNP_PWM_EN_RAMP_GEN_SHIFT 1
|
||
|
#define QPNP_PWM_EN_RAMP_GEN_MASK 0x02
|
||
|
|
||
|
#define QPNP_ENABLE_PWM(value) \
|
||
|
(value |= (1 << QPNP_EN_PWM_OUTPUT_SHIFT) & QPNP_EN_PWM_OUTPUT_MASK)
|
||
|
|
||
|
#define QPNP_DISABLE_PWM(value) (value &= ~QPNP_EN_PWM_OUTPUT_MASK)
|
||
|
|
||
|
/* LPG Control for RAMP_CONTROL */
|
||
|
#define QPNP_RAMP_START_MASK 0x01
|
||
|
|
||
|
#define QPNP_ENABLE_LUT_V0(value) (value |= QPNP_RAMP_START_MASK)
|
||
|
#define QPNP_DISABLE_LUT_V0(value) (value &= ~QPNP_RAMP_START_MASK)
|
||
|
#define QPNP_ENABLE_LUT_V1(value, id) (value |= BIT(id))
|
||
|
#define QPNP_DISABLE_LUT_V1(value, id) (value &= ~BIT(id))
|
||
|
|
||
|
/* LPG Control for RAMP_STEP_DURATION_LSB */
|
||
|
#define QPNP_RAMP_STEP_DURATION_LSB_MASK 0xFF
|
||
|
|
||
|
/* LPG Control for RAMP_STEP_DURATION_MSB */
|
||
|
#define QPNP_RAMP_STEP_DURATION_MSB_SHIFT 8
|
||
|
#define QPNP_RAMP_STEP_DURATION_MSB_MASK 0x01
|
||
|
|
||
|
#define QPNP_PWM_1KHZ 1024
|
||
|
#define QPNP_GET_RAMP_STEP_DURATION(ramp_time_ms) \
|
||
|
((ramp_time_ms * QPNP_PWM_1KHZ) / 1000)
|
||
|
|
||
|
/* LPG Control for PAUSE_HI_MULTIPLIER_LSB */
|
||
|
#define QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK 0xFF
|
||
|
|
||
|
/* LPG Control for PAUSE_HI_MULTIPLIER_MSB */
|
||
|
#define QPNP_PAUSE_HI_MULTIPLIER_MSB_SHIFT 8
|
||
|
#define QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK 0x1F
|
||
|
|
||
|
/* LPG Control for PAUSE_LO_MULTIPLIER_LSB */
|
||
|
#define QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK 0xFF
|
||
|
|
||
|
/* LPG Control for PAUSE_LO_MULTIPLIER_MSB */
|
||
|
#define QPNP_PAUSE_LO_MULTIPLIER_MSB_SHIFT 8
|
||
|
#define QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK 0x1F
|
||
|
|
||
|
/* LPG Control for HI_INDEX */
|
||
|
#define QPNP_HI_INDEX_MASK 0x3F
|
||
|
|
||
|
/* LPG Control for LO_INDEX */
|
||
|
#define QPNP_LO_INDEX_MASK 0x3F
|
||
|
|
||
|
#define NUM_CLOCKS 3
|
||
|
#define QPNP_PWM_M_MAX 7
|
||
|
#define NSEC_1024HZ (NSEC_PER_SEC / 1024)
|
||
|
#define NSEC_32768HZ (NSEC_PER_SEC / 32768)
|
||
|
#define NSEC_19P2MHZ (NSEC_PER_SEC / 19200000)
|
||
|
|
||
|
#define NUM_LPG_PRE_DIVIDE 4
|
||
|
|
||
|
#define PRE_DIVIDE_1 1
|
||
|
#define PRE_DIVIDE_3 3
|
||
|
#define PRE_DIVIDE_5 5
|
||
|
#define PRE_DIVIDE_6 6
|
||
|
|
||
|
#define SPMI_LPG_REG_BASE_OFFSET 0x40
|
||
|
#define SPMI_LPG_REVISION2_OFFSET 0x1
|
||
|
#define SPMI_LPG_REV1_RAMP_CONTROL_OFFSET 0x86
|
||
|
#define SPMI_LPG_REG_ADDR(b, n) (b + SPMI_LPG_REG_BASE_OFFSET + (n))
|
||
|
#define SPMI_MAX_BUF_LEN 8
|
||
|
|
||
|
/* LPG revisions */
|
||
|
enum qpnp_lpg_revision {
|
||
|
QPNP_LPG_REVISION_0 = 0x0,
|
||
|
QPNP_LPG_REVISION_1 = 0x1,
|
||
|
};
|
||
|
|
||
|
/* LPG LUT MODE STATE */
|
||
|
enum qpnp_lut_state {
|
||
|
QPNP_LUT_ENABLE = 0x0,
|
||
|
QPNP_LUT_DISABLE = 0x1,
|
||
|
};
|
||
|
|
||
|
/* PWM MODE STATE */
|
||
|
enum qpnp_pwm_state {
|
||
|
QPNP_PWM_ENABLE = 0x0,
|
||
|
QPNP_PWM_DISABLE = 0x1,
|
||
|
};
|
||
|
|
||
|
/* SPMI LPG registers */
|
||
|
enum qpnp_lpg_registers_list {
|
||
|
QPNP_LPG_PATTERN_CONFIG,
|
||
|
QPNP_LPG_PWM_SIZE_CLK,
|
||
|
QPNP_LPG_PWM_FREQ_PREDIV_CLK,
|
||
|
QPNP_LPG_PWM_TYPE_CONFIG,
|
||
|
QPNP_PWM_VALUE_LSB,
|
||
|
QPNP_PWM_VALUE_MSB,
|
||
|
QPNP_ENABLE_CONTROL,
|
||
|
QPNP_RAMP_CONTROL,
|
||
|
QPNP_RAMP_STEP_DURATION_LSB = QPNP_RAMP_CONTROL + 9,
|
||
|
QPNP_RAMP_STEP_DURATION_MSB,
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_LSB,
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_MSB,
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_LSB,
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_MSB,
|
||
|
QPNP_HI_INDEX,
|
||
|
QPNP_LO_INDEX,
|
||
|
QPNP_TOTAL_LPG_SPMI_REGISTERS
|
||
|
};
|
||
|
|
||
|
/*
|
||
|
* Formula from HSID,
|
||
|
* pause_time (hi/lo) = (pause_cnt- 1)*(ramp_ms)
|
||
|
* OR,
|
||
|
* pause_cnt = (pause_time / ramp_ms) + 1
|
||
|
*/
|
||
|
#define QPNP_SET_PAUSE_CNT(to_pause_cnt, from_pause, ramp_ms) \
|
||
|
(to_pause_cnt = (from_pause / (ramp_ms ? ramp_ms : 1)) + 1)
|
||
|
|
||
|
|
||
|
static unsigned int pt_t[NUM_LPG_PRE_DIVIDE][NUM_CLOCKS] = {
|
||
|
{ PRE_DIVIDE_1 * NSEC_1024HZ,
|
||
|
PRE_DIVIDE_1 * NSEC_32768HZ,
|
||
|
PRE_DIVIDE_1 * NSEC_19P2MHZ,
|
||
|
},
|
||
|
{ PRE_DIVIDE_3 * NSEC_1024HZ,
|
||
|
PRE_DIVIDE_3 * NSEC_32768HZ,
|
||
|
PRE_DIVIDE_3 * NSEC_19P2MHZ,
|
||
|
},
|
||
|
{ PRE_DIVIDE_5 * NSEC_1024HZ,
|
||
|
PRE_DIVIDE_5 * NSEC_32768HZ,
|
||
|
PRE_DIVIDE_5 * NSEC_19P2MHZ,
|
||
|
},
|
||
|
{ PRE_DIVIDE_6 * NSEC_1024HZ,
|
||
|
PRE_DIVIDE_6 * NSEC_32768HZ,
|
||
|
PRE_DIVIDE_6 * NSEC_19P2MHZ,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static RADIX_TREE(lpg_dev_tree, GFP_KERNEL);
|
||
|
|
||
|
struct qpnp_lut_config {
|
||
|
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 {
|
||
|
struct qpnp_lut_config lut_config;
|
||
|
u16 base_addr;
|
||
|
u16 lut_base_addr;
|
||
|
u16 lut_size;
|
||
|
};
|
||
|
|
||
|
struct qpnp_pwm_config {
|
||
|
int channel_id;
|
||
|
bool in_use;
|
||
|
const char *lable;
|
||
|
int pwm_value;
|
||
|
int pwm_period;
|
||
|
int pwm_duty;
|
||
|
struct pwm_period_config period;
|
||
|
};
|
||
|
|
||
|
/* Public facing structure */
|
||
|
struct pwm_device {
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
struct qpnp_pwm_config pwm_config;
|
||
|
};
|
||
|
|
||
|
struct qpnp_lpg_chip {
|
||
|
struct spmi_device *spmi_dev;
|
||
|
struct pwm_device pwm_dev;
|
||
|
spinlock_t lpg_lock;
|
||
|
struct qpnp_lpg_config lpg_config;
|
||
|
u8 qpnp_lpg_registers[QPNP_TOTAL_LPG_SPMI_REGISTERS];
|
||
|
enum qpnp_lpg_revision revision;
|
||
|
};
|
||
|
|
||
|
/* Internal functions */
|
||
|
static inline void qpnp_set_pattern_config(u8 *val,
|
||
|
struct qpnp_lut_config *lut_config)
|
||
|
{
|
||
|
*val = lut_config->enable_pause_lo & QPNP_EN_PAUSE_LO_MASK;
|
||
|
*val |= (lut_config->enable_pause_hi << QPNP_EN_PAUSE_HI_SHIFT) &
|
||
|
QPNP_EN_PAUSE_HI_MASK;
|
||
|
*val |= (lut_config->ramp_toggle << QPNP_RAMP_TOGGLE_SHIFT) &
|
||
|
QPNP_RAMP_TOGGLE_MASK;
|
||
|
*val |= (lut_config->pattern_repeat << QPNP_PATTERN_REPEAT_SHIFT) &
|
||
|
QPNP_PATTERN_REPEAT_MASK;
|
||
|
*val |= (lut_config->ramp_direction << QPNP_RAMP_DIRECTION_SHIFT) &
|
||
|
QPNP_RAMP_DIRECTION_MASK;
|
||
|
}
|
||
|
|
||
|
static inline void qpnp_set_pwm_type_config(u8 *val, bool glitch,
|
||
|
bool full_scale, bool en_phase, bool phase)
|
||
|
{
|
||
|
*val = phase;
|
||
|
*val |= (en_phase << QPNP_EN_PHASE_STAGGER_SHIFT) &
|
||
|
QPNP_EN_PHASE_STAGGER_MASK;
|
||
|
*val |= (full_scale << QPNP_EN_FULL_SCALE_SHIFT) &
|
||
|
QPNP_EN_FULL_SCALE_MASK;
|
||
|
*val |= (glitch << QPNP_EN_GLITCH_REMOVAL_SHIFT) &
|
||
|
QPNP_EN_GLITCH_REMOVAL_MASK;
|
||
|
}
|
||
|
|
||
|
static int qpnp_set_control(bool pwm_hi, bool pwm_lo, bool pwm_out,
|
||
|
bool pwm_src, bool ramp_gen)
|
||
|
{
|
||
|
return (ramp_gen << QPNP_PWM_EN_RAMP_GEN_SHIFT)
|
||
|
| (pwm_src << QPNP_PWM_SRC_SELECT_SHIFT)
|
||
|
| (pwm_out << QPNP_EN_PWM_OUTPUT_SHIFT)
|
||
|
| (pwm_lo << QPNP_EN_PWM_LO_SHIFT)
|
||
|
| (pwm_hi << QPNP_EN_PWM_HIGH_SHIFT);
|
||
|
}
|
||
|
|
||
|
#define QPNP_ENABLE_LUT_CONTROL qpnp_set_control(0, 0, 0, 0, 1)
|
||
|
#define QPNP_ENABLE_PWM_CONTROL qpnp_set_control(0, 0, 0, 1, 0)
|
||
|
#define QPNP_ENABLE_PWM_MODE qpnp_set_control(1, 1, 1, 1, 0)
|
||
|
#define QPNP_ENABLE_PWM_MODE_GPLED_CHANNEL qpnp_set_control(1, 1, 1, 1, 1)
|
||
|
#define QPNP_ENABLE_LPG_MODE qpnp_set_control(1, 1, 1, 0, 1)
|
||
|
#define QPNP_DISABLE_PWM_MODE qpnp_set_control(0, 0, 0, 1, 0)
|
||
|
#define QPNP_DISABLE_LPG_MODE qpnp_set_control(0, 0, 0, 0, 1)
|
||
|
#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)
|
||
|
{
|
||
|
*flags = ((l_config->ramp_direction ? PM_PWM_LUT_RAMP_UP : 0) |
|
||
|
(l_config->pattern_repeat ? PM_PWM_LUT_LOOP : 0)|
|
||
|
(l_config->ramp_toggle ? PM_PWM_LUT_REVERSE : 0) |
|
||
|
(l_config->enable_pause_hi ? PM_PWM_LUT_PAUSE_HI_EN : 0) |
|
||
|
(l_config->enable_pause_lo ? PM_PWM_LUT_PAUSE_LO_EN : 0));
|
||
|
}
|
||
|
|
||
|
static inline void qpnp_set_lut_params(struct lut_params *l_params,
|
||
|
struct qpnp_lut_config *l_config, int s_idx, int 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;
|
||
|
qpnp_convert_to_lut_flags(&l_params->flags, l_config);
|
||
|
}
|
||
|
|
||
|
static void qpnp_lpg_save(u8 *u8p, u8 mask, u8 val)
|
||
|
{
|
||
|
*u8p &= ~mask;
|
||
|
*u8p |= val & mask;
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_save_and_write(u8 value, u8 mask, u8 *reg, u16 addr,
|
||
|
u16 size, struct qpnp_lpg_chip *chip)
|
||
|
{
|
||
|
qpnp_lpg_save(reg, mask, value);
|
||
|
|
||
|
return spmi_ext_register_writel(chip->spmi_dev->ctrl,
|
||
|
chip->spmi_dev->sid, addr, reg, size);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* PWM Frequency = Clock Frequency / (N * T)
|
||
|
* or
|
||
|
* PWM Period = Clock Period * (N * T)
|
||
|
* where
|
||
|
* N = 2^9 or 2^6 for 9-bit or 6-bit PWM size
|
||
|
* T = Pre-divide * 2^m, where m = 0..7 (exponent)
|
||
|
*
|
||
|
* This is the formula to figure out m for the best pre-divide and clock:
|
||
|
* (PWM Period / N) = (Pre-divide * Clock Period) * 2^m
|
||
|
*/
|
||
|
static void qpnp_lpg_calc_period(unsigned int period_us,
|
||
|
struct pwm_period_config *period)
|
||
|
{
|
||
|
int n, m, clk, div;
|
||
|
int best_m, best_div, best_clk;
|
||
|
unsigned int last_err, cur_err, min_err;
|
||
|
unsigned int tmp_p, period_n;
|
||
|
|
||
|
/* PWM Period / N */
|
||
|
if (period_us < ((unsigned)(-1) / NSEC_PER_USEC)) {
|
||
|
period_n = (period_us * NSEC_PER_USEC) >> 6;
|
||
|
n = 6;
|
||
|
} else {
|
||
|
period_n = (period_us >> 9) * NSEC_PER_USEC;
|
||
|
n = 9;
|
||
|
}
|
||
|
|
||
|
min_err = last_err = (unsigned)(-1);
|
||
|
best_m = 0;
|
||
|
best_clk = 0;
|
||
|
best_div = 0;
|
||
|
for (clk = 0; clk < NUM_CLOCKS; clk++) {
|
||
|
for (div = 0; div < NUM_LPG_PRE_DIVIDE; div++) {
|
||
|
/* period_n = (PWM Period / N) */
|
||
|
/* tmp_p = (Pre-divide * Clock Period) * 2^m */
|
||
|
tmp_p = pt_t[div][clk];
|
||
|
for (m = 0; m <= QPNP_PWM_M_MAX; m++) {
|
||
|
if (period_n > tmp_p)
|
||
|
cur_err = period_n - tmp_p;
|
||
|
else
|
||
|
cur_err = tmp_p - period_n;
|
||
|
|
||
|
if (cur_err < min_err) {
|
||
|
min_err = cur_err;
|
||
|
best_m = m;
|
||
|
best_clk = clk;
|
||
|
best_div = div;
|
||
|
}
|
||
|
|
||
|
if (m && cur_err > last_err)
|
||
|
/* Break for bigger cur_err */
|
||
|
break;
|
||
|
|
||
|
last_err = cur_err;
|
||
|
tmp_p <<= 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Use higher resolution */
|
||
|
if (best_m >= 3 && n == 6) {
|
||
|
n += 3;
|
||
|
best_m -= 3;
|
||
|
}
|
||
|
|
||
|
period->pwm_size = n;
|
||
|
period->clk = best_clk;
|
||
|
period->pre_div = best_div;
|
||
|
period->pre_div_exp = best_m;
|
||
|
}
|
||
|
|
||
|
static void qpnp_lpg_calc_pwm_value(struct pwm_device *pwm,
|
||
|
unsigned int period_us,
|
||
|
unsigned int duty_us)
|
||
|
{
|
||
|
unsigned int max_pwm_value, tmp;
|
||
|
struct qpnp_pwm_config *pwm_config = &pwm->pwm_config;
|
||
|
|
||
|
/* Figure out pwm_value with overflow handling */
|
||
|
tmp = 1 << (sizeof(tmp) * 8 - pwm_config->period.pwm_size);
|
||
|
if (duty_us < tmp) {
|
||
|
tmp = duty_us << pwm_config->period.pwm_size;
|
||
|
pwm_config->pwm_value = tmp / period_us;
|
||
|
} else {
|
||
|
tmp = period_us >> pwm_config->period.pwm_size;
|
||
|
pwm_config->pwm_value = duty_us / tmp;
|
||
|
}
|
||
|
max_pwm_value = (1 << pwm_config->period.pwm_size) - 1;
|
||
|
if (pwm_config->pwm_value > max_pwm_value)
|
||
|
pwm_config->pwm_value = max_pwm_value;
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_change_table(struct pwm_device *pwm,
|
||
|
int duty_pct[], int raw_value)
|
||
|
{
|
||
|
unsigned int pwm_value, max_pwm_value;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
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_len << 1;
|
||
|
int offset = (lut->lo_index << 1) - 2;
|
||
|
|
||
|
pwm_size = QPNP_GET_PWM_SIZE(
|
||
|
chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) &
|
||
|
QPNP_PWM_SIZE_9_BIT ? 9 : 6;
|
||
|
|
||
|
max_pwm_value = (1 << pwm_size) - 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_len);
|
||
|
pr_err("However, index size is: %d\n",
|
||
|
(lut->hi_index - lut->lo_index + 1));
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < lut->list_len; i++) {
|
||
|
if (raw_value)
|
||
|
pwm_value = duty_pct[i];
|
||
|
else
|
||
|
pwm_value = (duty_pct[i] << pwm_size) / 100;
|
||
|
|
||
|
if (pwm_value > max_pwm_value)
|
||
|
pwm_value = max_pwm_value;
|
||
|
|
||
|
lut->duty_pct_list[i*2] = pwm_value;
|
||
|
lut->duty_pct_list[(i*2)+1] = (pwm_value >>
|
||
|
QPNP_PWM_VALUE_MSB_SHIFT) & QPNP_PWM_VALUE_MSB_MASK;
|
||
|
}
|
||
|
|
||
|
/* Write with max allowable burst mode, each entry is of two bytes */
|
||
|
for (i = 0; i < list_len;) {
|
||
|
if (i + burst_size >= list_len)
|
||
|
burst_size = list_len - i;
|
||
|
rc = spmi_ext_register_writel(chip->spmi_dev->ctrl,
|
||
|
chip->spmi_dev->sid,
|
||
|
chip->lpg_config.lut_base_addr + offset + i,
|
||
|
lut->duty_pct_list + i, burst_size);
|
||
|
i += burst_size;
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static void qpnp_lpg_save_period(struct pwm_device *pwm)
|
||
|
{
|
||
|
u8 mask, val;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
struct qpnp_pwm_config *pwm_config = &pwm->pwm_config;
|
||
|
|
||
|
QPNP_SET_PWM_CLK(val, pwm_config->period.clk,
|
||
|
pwm_config->period.pwm_size);
|
||
|
|
||
|
mask = QPNP_PWM_SIZE_MASK | QPNP_PWM_FREQ_CLK_SELECT_MASK;
|
||
|
|
||
|
qpnp_lpg_save(&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK],
|
||
|
mask, val);
|
||
|
|
||
|
QPNP_SET_PWM_FREQ_PREDIV(val, pwm_config->period.pre_div,
|
||
|
pwm_config->period.pre_div_exp);
|
||
|
|
||
|
mask = QPNP_PWM_FREQ_PRE_DIVIDE_MASK | QPNP_PWM_FREQ_EXP_MASK;
|
||
|
|
||
|
qpnp_lpg_save(&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK],
|
||
|
mask, val);
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_save_pwm_value(struct pwm_device *pwm)
|
||
|
{
|
||
|
unsigned int max_pwm_value;
|
||
|
int pwm_size;
|
||
|
u8 mask, value;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
struct qpnp_pwm_config *pwm_config = &pwm->pwm_config;
|
||
|
struct qpnp_lpg_config *lpg_config = &chip->lpg_config;
|
||
|
int rc;
|
||
|
|
||
|
pwm_size = QPNP_GET_PWM_SIZE(
|
||
|
chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK]) &
|
||
|
QPNP_PWM_SIZE_9_BIT ? 9 : 6;
|
||
|
|
||
|
max_pwm_value = (1 << pwm_size) - 1;
|
||
|
|
||
|
if (pwm_config->pwm_value > max_pwm_value)
|
||
|
pwm_config->pwm_value = max_pwm_value;
|
||
|
|
||
|
value = pwm_config->pwm_value;
|
||
|
mask = QPNP_PWM_VALUE_LSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PWM_VALUE_LSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PWM_VALUE_LSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
value = (pwm_config->pwm_value >> QPNP_PWM_VALUE_MSB_SHIFT) &
|
||
|
QPNP_PWM_VALUE_MSB_MASK;
|
||
|
|
||
|
mask = QPNP_PWM_VALUE_MSB_MASK;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PWM_VALUE_MSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PWM_VALUE_MSB), 1, chip);
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_pattern(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lut_config *lut_config = &lpg_config->lut_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
u8 value, mask;
|
||
|
|
||
|
qpnp_set_pattern_config(&value, lut_config);
|
||
|
|
||
|
mask = QPNP_RAMP_DIRECTION_MASK | QPNP_PATTERN_REPEAT_MASK |
|
||
|
QPNP_RAMP_TOGGLE_MASK | QPNP_EN_PAUSE_HI_MASK |
|
||
|
QPNP_EN_PAUSE_LO_MASK;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_LPG_PATTERN_CONFIG],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_LPG_PATTERN_CONFIG), 1, chip);
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_pwm(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
int rc;
|
||
|
u8 value, mask;
|
||
|
|
||
|
rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr, QPNP_LPG_PWM_SIZE_CLK),
|
||
|
&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK], 1);
|
||
|
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_LPG_PWM_FREQ_PREDIV_CLK),
|
||
|
&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK], 1);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
qpnp_set_pwm_type_config(&value, 1, 0, 0, 0);
|
||
|
|
||
|
mask = QPNP_EN_GLITCH_REMOVAL_MASK | QPNP_EN_FULL_SCALE_MASK |
|
||
|
QPNP_EN_PHASE_STAGGER_MASK | QPNP_PHASE_STAGGER_MASK;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_LPG_PWM_TYPE_CONFIG],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_LPG_PWM_TYPE_CONFIG), 1, chip);
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
u8 value, mask;
|
||
|
|
||
|
value = QPNP_ENABLE_PWM_CONTROL;
|
||
|
|
||
|
mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
|
||
|
QPNP_EN_PWM_OUTPUT_MASK | QPNP_PWM_SRC_SELECT_MASK |
|
||
|
QPNP_PWM_EN_RAMP_GEN_MASK;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_ENABLE_CONTROL), 1, chip);
|
||
|
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
u8 value, mask;
|
||
|
|
||
|
value = QPNP_ENABLE_LUT_CONTROL;
|
||
|
|
||
|
mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
|
||
|
QPNP_EN_PWM_OUTPUT_MASK | QPNP_PWM_SRC_SELECT_MASK |
|
||
|
QPNP_PWM_EN_RAMP_GEN_MASK;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_ENABLE_CONTROL), 1, chip);
|
||
|
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_ramp_step_duration(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lut_config lut_config = lpg_config->lut_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
int rc, value;
|
||
|
u8 val, mask;
|
||
|
|
||
|
value = QPNP_GET_RAMP_STEP_DURATION(lut_config.ramp_step_ms);
|
||
|
val = value & QPNP_RAMP_STEP_DURATION_LSB_MASK;
|
||
|
mask = QPNP_RAMP_STEP_DURATION_LSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(val, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_LSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_RAMP_STEP_DURATION_LSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
val = (value >> QPNP_RAMP_STEP_DURATION_MSB_SHIFT) &
|
||
|
QPNP_RAMP_STEP_DURATION_MSB_MASK;
|
||
|
|
||
|
mask = QPNP_RAMP_STEP_DURATION_MSB_MASK;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(val, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_RAMP_STEP_DURATION_MSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_RAMP_STEP_DURATION_MSB), 1, chip);
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_pause(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lut_config lut_config = lpg_config->lut_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
u8 value, mask;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (lut_config.enable_pause_hi) {
|
||
|
value = lut_config.lut_pause_hi_cnt;
|
||
|
mask = QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_LSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
value = (lut_config.lut_pause_hi_cnt >>
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_MSB_SHIFT) &
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
|
||
|
|
||
|
mask = QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_MSB), 1, chip);
|
||
|
} else {
|
||
|
value = 0;
|
||
|
mask = QPNP_PAUSE_HI_MULTIPLIER_LSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_LSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_LSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
mask = QPNP_PAUSE_HI_MULTIPLIER_MSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_HI_MULTIPLIER_MSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_HI_MULTIPLIER_MSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (lut_config.enable_pause_lo) {
|
||
|
value = lut_config.lut_pause_lo_cnt;
|
||
|
mask = QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_LSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
value = (lut_config.lut_pause_lo_cnt >>
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_MSB_SHIFT) &
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
|
||
|
|
||
|
mask = QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_MSB), 1, chip);
|
||
|
} else {
|
||
|
value = 0;
|
||
|
mask = QPNP_PAUSE_LO_MULTIPLIER_LSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_LSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_LSB), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
mask = QPNP_PAUSE_LO_MULTIPLIER_MSB_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_PAUSE_LO_MULTIPLIER_MSB],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_PAUSE_LO_MULTIPLIER_MSB), 1, chip);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_index(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lut_config lut_config = lpg_config->lut_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
u8 value, mask;
|
||
|
int rc = 0;
|
||
|
|
||
|
value = lut_config.hi_index;
|
||
|
mask = QPNP_HI_INDEX_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_HI_INDEX],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_HI_INDEX), 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
value = lut_config.lo_index;
|
||
|
mask = QPNP_LO_INDEX_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_LO_INDEX],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_LO_INDEX), 1, chip);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_change_lut(struct pwm_device *pwm)
|
||
|
{
|
||
|
int rc;
|
||
|
|
||
|
rc = qpnp_lpg_configure_pattern(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to configure LUT pattern");
|
||
|
return rc;
|
||
|
}
|
||
|
rc = qpnp_lpg_configure_pwm(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to configure LUT pattern");
|
||
|
return rc;
|
||
|
}
|
||
|
rc = qpnp_configure_lpg_control(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to configure pause registers");
|
||
|
return rc;
|
||
|
}
|
||
|
rc = qpnp_lpg_configure_ramp_step_duration(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to configure duty time");
|
||
|
return rc;
|
||
|
}
|
||
|
rc = qpnp_lpg_configure_pause(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to configure pause registers");
|
||
|
return rc;
|
||
|
}
|
||
|
rc = qpnp_lpg_configure_index(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to configure index registers");
|
||
|
return rc;
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_lut_state(struct pwm_device *pwm,
|
||
|
enum qpnp_lut_state state)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
u8 value1, value2, mask1, mask2;
|
||
|
u8 *reg1, *reg2;
|
||
|
u16 addr, addr1;
|
||
|
int rc;
|
||
|
|
||
|
value1 = pwm->chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
|
||
|
reg1 = &pwm->chip->qpnp_lpg_registers[QPNP_RAMP_CONTROL];
|
||
|
reg2 = &pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL];
|
||
|
mask2 = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
|
||
|
QPNP_EN_PWM_OUTPUT_MASK | QPNP_PWM_SRC_SELECT_MASK |
|
||
|
QPNP_PWM_EN_RAMP_GEN_MASK;
|
||
|
|
||
|
switch (chip->revision) {
|
||
|
case QPNP_LPG_REVISION_0:
|
||
|
if (state == QPNP_LUT_ENABLE) {
|
||
|
QPNP_ENABLE_LUT_V0(value1);
|
||
|
value2 = QPNP_ENABLE_LPG_MODE;
|
||
|
} else {
|
||
|
QPNP_DISABLE_LUT_V0(value1);
|
||
|
value2 = QPNP_DISABLE_LPG_MODE;
|
||
|
}
|
||
|
mask1 = QPNP_RAMP_START_MASK;
|
||
|
addr1 = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_RAMP_CONTROL);
|
||
|
break;
|
||
|
case QPNP_LPG_REVISION_1:
|
||
|
if (state == QPNP_LUT_ENABLE) {
|
||
|
QPNP_ENABLE_LUT_V1(value1, pwm->pwm_config.channel_id);
|
||
|
value2 = QPNP_ENABLE_LPG_MODE;
|
||
|
} else {
|
||
|
QPNP_DISABLE_LUT_V1(value1, pwm->pwm_config.channel_id);
|
||
|
value2 = QPNP_DISABLE_LPG_MODE;
|
||
|
}
|
||
|
mask1 = value1;
|
||
|
addr1 = lpg_config->lut_base_addr +
|
||
|
SPMI_LPG_REV1_RAMP_CONTROL_OFFSET;
|
||
|
break;
|
||
|
default:
|
||
|
pr_err("Invalid LPG revision\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
addr = SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_ENABLE_CONTROL);
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value2, mask2, reg2,
|
||
|
addr, 1, chip);
|
||
|
if (rc)
|
||
|
return rc;
|
||
|
|
||
|
return qpnp_lpg_save_and_write(value1, mask1, reg1,
|
||
|
addr1, 1, chip);
|
||
|
}
|
||
|
|
||
|
#define QPNP_GPLED_LPG_CHANNEL_RANGE_START 8
|
||
|
#define QPNP_GPLED_LPG_CHANNEL_RANGE_END 11
|
||
|
|
||
|
static inline int qpnp_enable_pwm_mode(struct qpnp_pwm_config *pwm_conf)
|
||
|
{
|
||
|
if (pwm_conf->channel_id >= QPNP_GPLED_LPG_CHANNEL_RANGE_START &&
|
||
|
pwm_conf->channel_id <= QPNP_GPLED_LPG_CHANNEL_RANGE_END)
|
||
|
return QPNP_ENABLE_PWM_MODE_GPLED_CHANNEL;
|
||
|
return QPNP_ENABLE_PWM_MODE;
|
||
|
}
|
||
|
|
||
|
static int qpnp_lpg_configure_pwm_state(struct pwm_device *pwm,
|
||
|
enum qpnp_pwm_state state)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config = &pwm->chip->lpg_config;
|
||
|
struct qpnp_lpg_chip *chip = pwm->chip;
|
||
|
u8 value, mask;
|
||
|
int rc;
|
||
|
|
||
|
if (state == QPNP_PWM_ENABLE)
|
||
|
value = qpnp_enable_pwm_mode(&pwm->pwm_config);
|
||
|
else
|
||
|
value = QPNP_DISABLE_PWM_MODE;
|
||
|
|
||
|
mask = QPNP_EN_PWM_HIGH_MASK | QPNP_EN_PWM_LO_MASK |
|
||
|
QPNP_EN_PWM_OUTPUT_MASK | QPNP_PWM_SRC_SELECT_MASK |
|
||
|
QPNP_PWM_EN_RAMP_GEN_MASK;
|
||
|
|
||
|
rc = qpnp_lpg_save_and_write(value, mask,
|
||
|
&pwm->chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL],
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_ENABLE_CONTROL), 1, chip);
|
||
|
if (rc)
|
||
|
goto out;
|
||
|
|
||
|
/*
|
||
|
* Due to LPG hardware bug, in the PWM mode, having enabled PWM,
|
||
|
* We have to write PWM values one more time.
|
||
|
*/
|
||
|
if (state == QPNP_PWM_ENABLE)
|
||
|
return qpnp_lpg_save_pwm_value(pwm);
|
||
|
|
||
|
out:
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int _pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
|
||
|
{
|
||
|
struct qpnp_pwm_config *pwm_config;
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
struct pwm_period_config *period;
|
||
|
int rc;
|
||
|
|
||
|
chip = pwm->chip;
|
||
|
pwm_config = &pwm->pwm_config;
|
||
|
period = &pwm_config->period;
|
||
|
|
||
|
if (pwm_config->pwm_period != period_us) {
|
||
|
qpnp_lpg_calc_period(period_us, period);
|
||
|
qpnp_lpg_save_period(pwm);
|
||
|
pwm_config->pwm_period = period_us;
|
||
|
}
|
||
|
|
||
|
pwm_config->pwm_duty = duty_us;
|
||
|
qpnp_lpg_calc_pwm_value(pwm, period_us, duty_us);
|
||
|
rc = qpnp_lpg_save_pwm_value(pwm);
|
||
|
|
||
|
if (rc) {
|
||
|
pr_err("Could not update PWM value for channel %d rc=%d\n",
|
||
|
pwm_config->channel_id, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
rc = qpnp_lpg_configure_pwm(pwm);
|
||
|
if (rc) {
|
||
|
pr_err("Could not configure PWM clock for\n");
|
||
|
pr_err("channel %d rc=%d\n", pwm_config->channel_id, rc);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int _pwm_lut_config(struct pwm_device *pwm, int period_us,
|
||
|
int duty_pct[], struct lut_params lut_params)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config;
|
||
|
struct qpnp_lut_config *lut_config;
|
||
|
struct pwm_period_config *period;
|
||
|
struct qpnp_pwm_config *pwm_config;
|
||
|
int start_idx = lut_params.start_idx;
|
||
|
int len = lut_params.idx_len;
|
||
|
int flags = lut_params.flags;
|
||
|
int raw_lut, ramp_step_ms;
|
||
|
int rc = 0;
|
||
|
|
||
|
pwm_config = &pwm->pwm_config;
|
||
|
lpg_config = &pwm->chip->lpg_config;
|
||
|
lut_config = &lpg_config->lut_config;
|
||
|
|
||
|
period = &pwm_config->period;
|
||
|
|
||
|
if (pwm_config->pwm_period != period_us) {
|
||
|
qpnp_lpg_calc_period(period_us, period);
|
||
|
qpnp_lpg_save_period(pwm);
|
||
|
pwm_config->pwm_period = period_us;
|
||
|
}
|
||
|
|
||
|
if (flags & PM_PWM_LUT_NO_TABLE)
|
||
|
goto after_table_write;
|
||
|
|
||
|
raw_lut = 0;
|
||
|
if (flags & PM_PWM_LUT_USE_RAW_VALUE)
|
||
|
raw_lut = 1;
|
||
|
|
||
|
lut_config->list_len = len;
|
||
|
lut_config->lo_index = start_idx + 1;
|
||
|
lut_config->hi_index = start_idx + len;
|
||
|
|
||
|
rc = qpnp_lpg_change_table(pwm, duty_pct, raw_lut);
|
||
|
if (rc) {
|
||
|
pr_err("qpnp_lpg_change_table: rc=%d\n", rc);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
after_table_write:
|
||
|
ramp_step_ms = lut_params.ramp_step_ms;
|
||
|
|
||
|
if (ramp_step_ms > PM_PWM_LUT_RAMP_STEP_TIME_MAX)
|
||
|
ramp_step_ms = PM_PWM_LUT_RAMP_STEP_TIME_MAX;
|
||
|
|
||
|
QPNP_SET_PAUSE_CNT(lut_config->lut_pause_lo_cnt,
|
||
|
lut_params.lut_pause_lo, ramp_step_ms);
|
||
|
if (lut_config->lut_pause_lo_cnt > PM_PWM_MAX_PAUSE_CNT)
|
||
|
lut_config->lut_pause_lo_cnt = PM_PWM_MAX_PAUSE_CNT;
|
||
|
|
||
|
QPNP_SET_PAUSE_CNT(lut_config->lut_pause_hi_cnt,
|
||
|
lut_params.lut_pause_hi, ramp_step_ms);
|
||
|
if (lut_config->lut_pause_hi_cnt > PM_PWM_MAX_PAUSE_CNT)
|
||
|
lut_config->lut_pause_hi_cnt = PM_PWM_MAX_PAUSE_CNT;
|
||
|
|
||
|
lut_config->ramp_step_ms = ramp_step_ms;
|
||
|
|
||
|
lut_config->ramp_direction = !!(flags & PM_PWM_LUT_RAMP_UP);
|
||
|
lut_config->pattern_repeat = !!(flags & PM_PWM_LUT_LOOP);
|
||
|
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);
|
||
|
|
||
|
rc = qpnp_lpg_change_lut(pwm);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int _pwm_enable(struct pwm_device *pwm)
|
||
|
{
|
||
|
int rc;
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
unsigned long flags;
|
||
|
|
||
|
chip = pwm->chip;
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (QPNP_IS_PWM_CONFIG_SELECTED(
|
||
|
chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]))
|
||
|
rc = qpnp_lpg_configure_pwm_state(pwm, QPNP_PWM_ENABLE);
|
||
|
else
|
||
|
rc = qpnp_lpg_configure_lut_state(pwm, QPNP_LUT_ENABLE);
|
||
|
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (rc)
|
||
|
pr_err("Failed to enable PWM channel: %d\n",
|
||
|
pwm->pwm_config.channel_id);
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/* APIs */
|
||
|
/**
|
||
|
* pwm_request - request a PWM device
|
||
|
* @channel_id: PWM id or channel
|
||
|
* @lable: the label to identify the user
|
||
|
*/
|
||
|
struct pwm_device *pwm_request(int pwm_id, const char *lable)
|
||
|
{
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
struct pwm_device *pwm;
|
||
|
unsigned long flags;
|
||
|
|
||
|
chip = radix_tree_lookup(&lpg_dev_tree, pwm_id);
|
||
|
|
||
|
if (!chip) {
|
||
|
pr_err("Could not find PWM Device for the\n");
|
||
|
pr_err("input pwm channel %d\n", pwm_id);
|
||
|
return ERR_PTR(-EINVAL);
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&chip->lpg_lock, flags);
|
||
|
|
||
|
pwm = &chip->pwm_dev;
|
||
|
|
||
|
if (pwm->pwm_config.in_use) {
|
||
|
pr_err("PWM device associated with the");
|
||
|
pr_err("input pwm id: %d is in use by %s",
|
||
|
pwm_id, pwm->pwm_config.lable);
|
||
|
pwm = ERR_PTR(-EBUSY);
|
||
|
} else {
|
||
|
pwm->pwm_config.in_use = 1;
|
||
|
pwm->pwm_config.lable = lable;
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&chip->lpg_lock, flags);
|
||
|
|
||
|
return pwm;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_request);
|
||
|
|
||
|
/**
|
||
|
* pwm_free - free a PWM device
|
||
|
* @pwm: the PWM device
|
||
|
*/
|
||
|
void pwm_free(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_pwm_config *pwm_config;
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
|
||
|
pr_err("Invalid pwm handle or no pwm_chip\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
pwm_config = &pwm->pwm_config;
|
||
|
|
||
|
if (pwm_config->in_use) {
|
||
|
qpnp_lpg_configure_pwm_state(pwm, QPNP_PWM_DISABLE);
|
||
|
qpnp_lpg_configure_lut_state(pwm, QPNP_LUT_DISABLE);
|
||
|
pwm_config->in_use = 0;
|
||
|
pwm_config->lable = NULL;
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_free);
|
||
|
|
||
|
/**
|
||
|
* pwm_config - change a PWM device configuration
|
||
|
* @pwm: the PWM device
|
||
|
* @period_us: period in microseconds
|
||
|
* @duty_us: duty cycle in microseconds
|
||
|
*/
|
||
|
int pwm_config(struct pwm_device *pwm, int duty_us, int period_us)
|
||
|
{
|
||
|
int rc;
|
||
|
unsigned long flags;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm) ||
|
||
|
duty_us > period_us ||
|
||
|
(unsigned)period_us > PM_PWM_PERIOD_MAX ||
|
||
|
(unsigned)period_us < PM_PWM_PERIOD_MIN) {
|
||
|
pr_err("Invalid pwm handle or parameters\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!pwm->pwm_config.in_use)
|
||
|
return -EINVAL;
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
rc = _pwm_config(pwm, duty_us, period_us);
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (rc)
|
||
|
pr_err("Failed to configure PWM mode\n");
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_config);
|
||
|
|
||
|
/**
|
||
|
* pwm_enable - start a PWM output toggling
|
||
|
* @pwm: the PWM device
|
||
|
*/
|
||
|
int pwm_enable(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_pwm_config *p_config;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
|
||
|
pr_err("Invalid pwm handle or no pwm_chip\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
p_config = &pwm->pwm_config;
|
||
|
|
||
|
if (!p_config->in_use) {
|
||
|
pr_err("channel_id: %d: stale handle?\n", p_config->channel_id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return _pwm_enable(pwm);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_enable);
|
||
|
|
||
|
/**
|
||
|
* pwm_disable - stop a PWM output toggling
|
||
|
* @pwm: the PWM device
|
||
|
*/
|
||
|
void pwm_disable(struct pwm_device *pwm)
|
||
|
{
|
||
|
struct qpnp_pwm_config *pwm_config;
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
unsigned long flags;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm) || pwm->chip == NULL) {
|
||
|
pr_err("Invalid pwm handle or no pwm_chip\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
chip = pwm->chip;
|
||
|
pwm_config = &pwm->pwm_config;
|
||
|
|
||
|
if (pwm_config->in_use) {
|
||
|
if (QPNP_IS_PWM_CONFIG_SELECTED(
|
||
|
chip->qpnp_lpg_registers[QPNP_ENABLE_CONTROL]))
|
||
|
rc = qpnp_lpg_configure_pwm_state(pwm,
|
||
|
QPNP_PWM_DISABLE);
|
||
|
else
|
||
|
rc = qpnp_lpg_configure_lut_state(pwm,
|
||
|
QPNP_LUT_DISABLE);
|
||
|
}
|
||
|
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (rc)
|
||
|
pr_err("Failed to disable PWM channel: %d\n",
|
||
|
pwm_config->channel_id);
|
||
|
}
|
||
|
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;
|
||
|
unsigned long flags;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (mode)
|
||
|
rc = qpnp_configure_lpg_control(pwm);
|
||
|
else
|
||
|
rc = qpnp_configure_pwm_control(pwm);
|
||
|
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (rc)
|
||
|
pr_err("Failed to change the mode\n");
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_change_mode);
|
||
|
|
||
|
/**
|
||
|
* pwm_config_period - change PWM period
|
||
|
*
|
||
|
* @pwm: the PWM device
|
||
|
* @pwm_p: period in struct qpnp_lpg_period
|
||
|
*/
|
||
|
int pwm_config_period(struct pwm_device *pwm,
|
||
|
struct pwm_period_config *period)
|
||
|
{
|
||
|
struct qpnp_pwm_config *pwm_config;
|
||
|
struct qpnp_lpg_config *lpg_config;
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
unsigned long flags;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm) || period == NULL)
|
||
|
return -EINVAL;
|
||
|
if (pwm->chip == NULL)
|
||
|
return -ENODEV;
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
chip = pwm->chip;
|
||
|
pwm_config = &pwm->pwm_config;
|
||
|
lpg_config = &chip->lpg_config;
|
||
|
|
||
|
if (!pwm_config->in_use) {
|
||
|
rc = -EINVAL;
|
||
|
goto out_unlock;
|
||
|
}
|
||
|
|
||
|
pwm_config->period.pwm_size = period->pwm_size;
|
||
|
pwm_config->period.clk = period->clk;
|
||
|
pwm_config->period.pre_div = period->pre_div;
|
||
|
pwm_config->period.pre_div_exp = period->pre_div_exp;
|
||
|
|
||
|
qpnp_lpg_save_period(pwm);
|
||
|
|
||
|
rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_LPG_PWM_SIZE_CLK),
|
||
|
&chip->qpnp_lpg_registers[QPNP_LPG_PWM_SIZE_CLK], 1);
|
||
|
|
||
|
if (rc) {
|
||
|
pr_err("Write failed: QPNP_LPG_PWM_SIZE_CLK register, rc: %d\n",
|
||
|
rc);
|
||
|
goto out_unlock;
|
||
|
}
|
||
|
|
||
|
rc = spmi_ext_register_writel(chip->spmi_dev->ctrl, chip->spmi_dev->sid,
|
||
|
SPMI_LPG_REG_ADDR(lpg_config->base_addr,
|
||
|
QPNP_LPG_PWM_FREQ_PREDIV_CLK),
|
||
|
&chip->qpnp_lpg_registers[QPNP_LPG_PWM_FREQ_PREDIV_CLK], 1);
|
||
|
if (rc) {
|
||
|
pr_err("Failed to write to QPNP_LPG_PWM_FREQ_PREDIV_CLK\n");
|
||
|
pr_err("register, rc = %d\n", rc);
|
||
|
}
|
||
|
|
||
|
out_unlock:
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL(pwm_config_period);
|
||
|
|
||
|
/**
|
||
|
* pwm_config_pwm_value - change a PWM device configuration
|
||
|
* @pwm: the PWM device
|
||
|
* @pwm_value: the duty cycle in raw PWM value (< 2^pwm_size)
|
||
|
*/
|
||
|
int pwm_config_pwm_value(struct pwm_device *pwm, int pwm_value)
|
||
|
{
|
||
|
struct qpnp_lpg_config *lpg_config;
|
||
|
struct qpnp_pwm_config *pwm_config;
|
||
|
unsigned long flags;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm)) {
|
||
|
pr_err("Invalid parameter passed\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (pwm->chip == NULL) {
|
||
|
pr_err("Invalid device handle\n");
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
lpg_config = &pwm->chip->lpg_config;
|
||
|
pwm_config = &pwm->pwm_config;
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (!pwm_config->in_use || !pwm_config->pwm_period) {
|
||
|
rc = -EINVAL;
|
||
|
pr_err("PWM channel isn't in use or period value missing\n");
|
||
|
goto out_unlock;
|
||
|
}
|
||
|
|
||
|
if (pwm_config->pwm_value == pwm_value)
|
||
|
goto out_unlock;
|
||
|
|
||
|
pwm_config->pwm_value = pwm_value;
|
||
|
|
||
|
rc = qpnp_lpg_save_pwm_value(pwm);
|
||
|
|
||
|
if (rc)
|
||
|
pr_err("Could not update PWM value for channel %d rc=%d\n",
|
||
|
pwm_config->channel_id, rc);
|
||
|
|
||
|
out_unlock:
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_config_pwm_value);
|
||
|
|
||
|
/**
|
||
|
* pwm_lut_config - change LPG LUT device configuration
|
||
|
* @pwm: the PWM device
|
||
|
* @period_us: period in micro second
|
||
|
* @duty_pct: array of duty cycles in percent, like 20, 50.
|
||
|
* @lut_params: Lookup table parameters
|
||
|
*/
|
||
|
int pwm_lut_config(struct pwm_device *pwm, int period_us,
|
||
|
int duty_pct[], struct lut_params lut_params)
|
||
|
{
|
||
|
unsigned long flags;
|
||
|
int rc = 0;
|
||
|
|
||
|
if (pwm == NULL || IS_ERR(pwm) || !lut_params.idx_len) {
|
||
|
pr_err("Invalid pwm handle or idx_len=0\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
spin_lock_irqsave(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
rc = _pwm_lut_config(pwm, period_us, duty_pct, lut_params);
|
||
|
|
||
|
spin_unlock_irqrestore(&pwm->chip->lpg_lock, flags);
|
||
|
|
||
|
if (rc)
|
||
|
pr_err("Failed to configure LUT\n");
|
||
|
|
||
|
return rc;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(pwm_lut_config);
|
||
|
|
||
|
static int qpnp_parse_pwm_dt_config(struct device_node *of_pwm_node,
|
||
|
struct device_node *of_parent, struct qpnp_lpg_chip *chip)
|
||
|
{
|
||
|
int rc, period;
|
||
|
struct pwm_device *pwm_dev = &chip->pwm_dev;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
duty_pct_list = kzalloc(sizeof(u32) * list_size, GFP_KERNEL);
|
||
|
|
||
|
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:
|
||
|
kfree(duty_pct_list);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
/* Fill in lpg device elements based on values found in device tree. */
|
||
|
static int qpnp_parse_dt_config(struct spmi_device *spmi,
|
||
|
struct qpnp_lpg_chip *chip)
|
||
|
{
|
||
|
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 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;
|
||
|
|
||
|
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__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
lpg_config->base_addr = res->start;
|
||
|
|
||
|
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__);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
lpg_config->lut_base_addr = res->start;
|
||
|
/* 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\n",
|
||
|
__func__);
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
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__);
|
||
|
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__);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
rc = of_property_read_u32(of_node, "qcom,mode-select", &enable);
|
||
|
if (rc)
|
||
|
goto read_opt_props;
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
pwm_change_mode(pwm_dev, enable);
|
||
|
_pwm_enable(pwm_dev);
|
||
|
|
||
|
read_opt_props:
|
||
|
/* Initialize optional config parameters from DT if provided */
|
||
|
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)
|
||
|
{
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
int rc, id;
|
||
|
|
||
|
chip = kzalloc(sizeof *chip, GFP_KERNEL);
|
||
|
if (chip == NULL) {
|
||
|
pr_err("kzalloc() failed.\n");
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
spin_lock_init(&chip->lpg_lock);
|
||
|
|
||
|
chip->spmi_dev = spmi;
|
||
|
chip->pwm_dev.chip = chip;
|
||
|
dev_set_drvdata(&spmi->dev, chip);
|
||
|
|
||
|
rc = qpnp_parse_dt_config(spmi, chip);
|
||
|
|
||
|
if (rc)
|
||
|
goto failed_config;
|
||
|
|
||
|
id = chip->pwm_dev.pwm_config.channel_id;
|
||
|
|
||
|
spmi_ext_register_readl(chip->spmi_dev->ctrl,
|
||
|
chip->spmi_dev->sid,
|
||
|
chip->lpg_config.base_addr + SPMI_LPG_REVISION2_OFFSET,
|
||
|
(u8 *) &chip->revision, 1);
|
||
|
|
||
|
if (chip->revision < QPNP_LPG_REVISION_0 ||
|
||
|
chip->revision > QPNP_LPG_REVISION_1) {
|
||
|
pr_err("Unknown LPG revision detected, rev:%d\n",
|
||
|
chip->revision);
|
||
|
rc = -EINVAL;
|
||
|
goto failed_insert;
|
||
|
}
|
||
|
|
||
|
rc = radix_tree_insert(&lpg_dev_tree, id, chip);
|
||
|
|
||
|
if (rc) {
|
||
|
dev_err(&spmi->dev, "%s: Failed to register LPG Channel %d\n",
|
||
|
__func__, id);
|
||
|
goto failed_insert;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
failed_insert:
|
||
|
kfree(chip->lpg_config.lut_config.duty_pct_list);
|
||
|
failed_config:
|
||
|
dev_set_drvdata(&spmi->dev, NULL);
|
||
|
kfree(chip);
|
||
|
return rc;
|
||
|
}
|
||
|
|
||
|
static int __devexit qpnp_pwm_remove(struct spmi_device *spmi)
|
||
|
{
|
||
|
struct qpnp_lpg_chip *chip;
|
||
|
struct qpnp_lpg_config *lpg_config;
|
||
|
|
||
|
chip = dev_get_drvdata(&spmi->dev);
|
||
|
|
||
|
dev_set_drvdata(&spmi->dev, NULL);
|
||
|
|
||
|
if (chip) {
|
||
|
lpg_config = &chip->lpg_config;
|
||
|
kfree(lpg_config->lut_config.duty_pct_list);
|
||
|
kfree(chip);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct of_device_id spmi_match_table[] = {
|
||
|
{ .compatible = QPNP_LPG_DRIVER_NAME, },
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
static const struct spmi_device_id qpnp_lpg_id[] = {
|
||
|
{ QPNP_LPG_DRIVER_NAME, 0 },
|
||
|
{ }
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(spmi, qpnp_lpg_id);
|
||
|
|
||
|
static struct spmi_driver qpnp_lpg_driver = {
|
||
|
.driver = {
|
||
|
.name = QPNP_LPG_DRIVER_NAME,
|
||
|
.of_match_table = spmi_match_table,
|
||
|
.owner = THIS_MODULE,
|
||
|
},
|
||
|
.probe = qpnp_pwm_probe,
|
||
|
.remove = __devexit_p(qpnp_pwm_remove),
|
||
|
.id_table = qpnp_lpg_id,
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* qpnp_lpg_init() - register spmi driver for qpnp-lpg
|
||
|
*/
|
||
|
int __init qpnp_lpg_init(void)
|
||
|
{
|
||
|
return spmi_driver_register(&qpnp_lpg_driver);
|
||
|
}
|
||
|
|
||
|
static void __exit qpnp_lpg_exit(void)
|
||
|
{
|
||
|
spmi_driver_unregister(&qpnp_lpg_driver);
|
||
|
}
|
||
|
|
||
|
MODULE_DESCRIPTION("QPNP PMIC LPG driver");
|
||
|
MODULE_LICENSE("GPL v2");
|
||
|
MODULE_ALIAS("platform:" QPNP_LPG_DRIVER_NAME);
|
||
|
|
||
|
subsys_initcall(qpnp_lpg_init);
|
||
|
module_exit(qpnp_lpg_exit);
|