mirror of
https://github.com/edk2-porting/linux-next.git
synced 2024-12-16 01:04:08 +08:00
pwm: Changes for v4.13-rc1
This release cycle's changes include mostly updates and cleanups to existing drivers along with a few cleanups to the core, documentation and device tree bindings. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAllnboQZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zocCFD/9kUnHzwyM3epnGQruCwgLI xwcZmk2vAImbLoMN0NwtLpKyxIxehDCjvgsRxAm5ggE7Whs2Wzs1tzdML863umdl DcfF46T+zHlUXNZMgES+Znp787G6Bqnjj35PNVqNneptukmrZakPMQ4nZkL0UidD dZnXs/KhK97G4ys00+uVSfS/x6Ck1oD9XUtJttSGx4f7wIhWcsiwUpJvi772jcG+ bVmVkbJsOnui4UdvUs1uCb5yOppzOJqwsuNCAyX9a3NKR6u3/RtETjo4DQHhqXrv mXoHEIZkljDOgni2Zu/kcUDjooQ/GJVLjwku2iq1UGmJTu6FOJKqSLBKGQzyCXs+ BN0JM7pwEwwGKFzKbNn/ZxpJD06Hk0ABWcjeObIgsZaUOGmUdiDzwpFmLtKK22TC 9zNEODo+OjuaNYKADa4RHZrDbujiPMCL9ezonO3Xb1AS9UQfR8wXsVFqR44EofQA 0x+q+yIS5GVMab6s/jN51G/RVW2Ao+SdT+0/F8QF8N3YlCFsFCwt+X96383oDPAj STiHxqIRzEkn44Ua9CFg9utTrQeUVtNB3tw0wnD+zgsMFH1YRzcU2p714viqN8tQ N0yJUi+VwHXxnfNiVZNdrgidRH7ETWXyJY/OFRt2BCq9VuZ8V3E/Dr41pgFxZ0e6 WP7awOH/n25e4L09CjRtng== =ioFX -----END PGP SIGNATURE----- Merge tag 'pwm/for-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm updates from Thierry Reding: "This release cycle's changes include mostly updates and cleanups to existing drivers along with a few cleanups to the core, documentation and device tree bindings" * tag 'pwm/for-4.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: pwm: cros-ec: Fix transposed param settings pwm: meson: Improve PWM calculation precision dt-bindings: pwm: meson: Add compatible for gxbb ao PWMs pwm: meson: Add compatible for the gxbb ao PWMs pwm: sun4i: Drop legacy callbacks pwm: sun4i: Switch to atomic PWM pwm: sun4i: Improve hardware read out pwm: hibvt: Constify hibvt_pwm_ops pwm: Silently error out on EPROBE_DEFER pwm: Standardize document format pwm: bfin: Remove unneeded error message dt-bindings: pwm: Update STM32 timers clock names dt-bindings: pwm: Add R-Car M3-W device tree bindings pwm: tegra: Set maximum pwm clock source per SoC tapeout
This commit is contained in:
commit
38f7d2da4e
@ -2,7 +2,9 @@ Amlogic Meson PWM Controller
|
||||
============================
|
||||
|
||||
Required properties:
|
||||
- compatible: Shall contain "amlogic,meson8b-pwm" or "amlogic,meson-gxbb-pwm".
|
||||
- compatible: Shall contain "amlogic,meson8b-pwm"
|
||||
or "amlogic,meson-gxbb-pwm"
|
||||
or "amlogic,meson-gxbb-ao-pwm"
|
||||
- #pwm-cells: Should be 3. See pwm.txt in this directory for a description of
|
||||
the cells format.
|
||||
|
||||
|
@ -24,7 +24,7 @@ Example:
|
||||
compatible = "st,stm32-timers";
|
||||
reg = <0x40010000 0x400>;
|
||||
clocks = <&rcc 0 160>;
|
||||
clock-names = "clk_int";
|
||||
clock-names = "int";
|
||||
|
||||
pwm {
|
||||
compatible = "st,stm32-pwm";
|
||||
|
@ -8,6 +8,7 @@ Required Properties:
|
||||
- "renesas,pwm-r8a7791": for R-Car M2-W
|
||||
- "renesas,pwm-r8a7794": for R-Car E2
|
||||
- "renesas,pwm-r8a7795": for R-Car H3
|
||||
- "renesas,pwm-r8a7796": for R-Car M3-W
|
||||
- reg: base address and length of the registers block for the PWM.
|
||||
- #pwm-cells: should be 2. See pwm.txt in this directory for a description of
|
||||
the cells format.
|
||||
|
@ -1,4 +1,6 @@
|
||||
======================================
|
||||
Pulse Width Modulation (PWM) interface
|
||||
======================================
|
||||
|
||||
This provides an overview about the Linux PWM interface
|
||||
|
||||
@ -16,7 +18,7 @@ Users of the legacy PWM API use unique IDs to refer to PWM devices.
|
||||
|
||||
Instead of referring to a PWM device via its unique ID, board setup code
|
||||
should instead register a static mapping that can be used to match PWM
|
||||
consumers to providers, as given in the following example:
|
||||
consumers to providers, as given in the following example::
|
||||
|
||||
static struct pwm_lookup board_pwm_lookup[] = {
|
||||
PWM_LOOKUP("tegra-pwm", 0, "pwm-backlight", NULL,
|
||||
@ -40,9 +42,9 @@ New users should use the pwm_get() function and pass to it the consumer
|
||||
device or a consumer name. pwm_put() is used to free the PWM device. Managed
|
||||
variants of these functions, devm_pwm_get() and devm_pwm_put(), also exist.
|
||||
|
||||
After being requested, a PWM has to be configured using:
|
||||
After being requested, a PWM has to be configured using::
|
||||
|
||||
int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state);
|
||||
int pwm_apply_state(struct pwm_device *pwm, struct pwm_state *state);
|
||||
|
||||
This API controls both the PWM period/duty_cycle config and the
|
||||
enable/disable state.
|
||||
@ -72,11 +74,14 @@ interface is provided to use the PWMs from userspace. It is exposed at
|
||||
pwmchipN, where N is the base of the PWM chip. Inside the directory you
|
||||
will find:
|
||||
|
||||
npwm - The number of PWM channels this chip supports (read-only).
|
||||
npwm
|
||||
The number of PWM channels this chip supports (read-only).
|
||||
|
||||
export - Exports a PWM channel for use with sysfs (write-only).
|
||||
export
|
||||
Exports a PWM channel for use with sysfs (write-only).
|
||||
|
||||
unexport - Unexports a PWM channel from sysfs (write-only).
|
||||
unexport
|
||||
Unexports a PWM channel from sysfs (write-only).
|
||||
|
||||
The PWM channels are numbered using a per-chip index from 0 to npwm-1.
|
||||
|
||||
@ -84,21 +89,26 @@ When a PWM channel is exported a pwmX directory will be created in the
|
||||
pwmchipN directory it is associated with, where X is the number of the
|
||||
channel that was exported. The following properties will then be available:
|
||||
|
||||
period - The total period of the PWM signal (read/write).
|
||||
Value is in nanoseconds and is the sum of the active and inactive
|
||||
time of the PWM.
|
||||
period
|
||||
The total period of the PWM signal (read/write).
|
||||
Value is in nanoseconds and is the sum of the active and inactive
|
||||
time of the PWM.
|
||||
|
||||
duty_cycle - The active time of the PWM signal (read/write).
|
||||
Value is in nanoseconds and must be less than the period.
|
||||
duty_cycle
|
||||
The active time of the PWM signal (read/write).
|
||||
Value is in nanoseconds and must be less than the period.
|
||||
|
||||
polarity - Changes the polarity of the PWM signal (read/write).
|
||||
Writes to this property only work if the PWM chip supports changing
|
||||
the polarity. The polarity can only be changed if the PWM is not
|
||||
enabled. Value is the string "normal" or "inversed".
|
||||
polarity
|
||||
Changes the polarity of the PWM signal (read/write).
|
||||
Writes to this property only work if the PWM chip supports changing
|
||||
the polarity. The polarity can only be changed if the PWM is not
|
||||
enabled. Value is the string "normal" or "inversed".
|
||||
|
||||
enable - Enable/disable the PWM signal (read/write).
|
||||
0 - disabled
|
||||
1 - enabled
|
||||
enable
|
||||
Enable/disable the PWM signal (read/write).
|
||||
|
||||
- 0 - disabled
|
||||
- 1 - enabled
|
||||
|
||||
Implementing a PWM driver
|
||||
-------------------------
|
||||
|
@ -678,7 +678,9 @@ struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id)
|
||||
|
||||
pc = of_node_to_pwmchip(args.np);
|
||||
if (IS_ERR(pc)) {
|
||||
pr_err("%s(): PWM chip not found\n", __func__);
|
||||
if (PTR_ERR(pc) != -EPROBE_DEFER)
|
||||
pr_err("%s(): PWM chip not found\n", __func__);
|
||||
|
||||
pwm = ERR_CAST(pc);
|
||||
goto put;
|
||||
}
|
||||
|
@ -118,10 +118,8 @@ static int bfin_pwm_probe(struct platform_device *pdev)
|
||||
int ret;
|
||||
|
||||
pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
|
||||
if (!pwm) {
|
||||
dev_err(&pdev->dev, "failed to allocate memory\n");
|
||||
if (!pwm)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, pwm);
|
||||
|
||||
|
@ -75,8 +75,8 @@ static int __cros_ec_pwm_get_duty(struct cros_ec_device *ec, u8 index,
|
||||
|
||||
msg->version = 0;
|
||||
msg->command = EC_CMD_PWM_GET_DUTY;
|
||||
msg->insize = sizeof(*params);
|
||||
msg->outsize = sizeof(*resp);
|
||||
msg->insize = sizeof(*resp);
|
||||
msg->outsize = sizeof(*params);
|
||||
|
||||
params->pwm_type = EC_PWM_TYPE_GENERIC;
|
||||
params->index = index;
|
||||
|
@ -165,7 +165,7 @@ static int hibvt_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct pwm_ops hibvt_pwm_ops = {
|
||||
static const struct pwm_ops hibvt_pwm_ops = {
|
||||
.get_state = hibvt_pwm_get_state,
|
||||
.apply = hibvt_pwm_apply,
|
||||
|
||||
|
@ -103,6 +103,7 @@ struct meson_pwm_channel {
|
||||
|
||||
struct meson_pwm_data {
|
||||
const char * const *parent_names;
|
||||
unsigned int num_parents;
|
||||
};
|
||||
|
||||
struct meson_pwm {
|
||||
@ -162,7 +163,8 @@ static int meson_pwm_calc(struct meson_pwm *meson,
|
||||
unsigned int duty, unsigned int period)
|
||||
{
|
||||
unsigned int pre_div, cnt, duty_cnt;
|
||||
unsigned long fin_freq = -1, fin_ns;
|
||||
unsigned long fin_freq = -1;
|
||||
u64 fin_ps;
|
||||
|
||||
if (~(meson->inverter_mask >> id) & 0x1)
|
||||
duty = period - duty;
|
||||
@ -178,13 +180,15 @@ static int meson_pwm_calc(struct meson_pwm *meson,
|
||||
}
|
||||
|
||||
dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq);
|
||||
fin_ns = NSEC_PER_SEC / fin_freq;
|
||||
fin_ps = (u64)NSEC_PER_SEC * 1000;
|
||||
do_div(fin_ps, fin_freq);
|
||||
|
||||
/* Calc pre_div with the period */
|
||||
for (pre_div = 0; pre_div < MISC_CLK_DIV_MASK; pre_div++) {
|
||||
cnt = DIV_ROUND_CLOSEST(period, fin_ns * (pre_div + 1));
|
||||
dev_dbg(meson->chip.dev, "fin_ns=%lu pre_div=%u cnt=%u\n",
|
||||
fin_ns, pre_div, cnt);
|
||||
cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000,
|
||||
fin_ps * (pre_div + 1));
|
||||
dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n",
|
||||
fin_ps, pre_div, cnt);
|
||||
if (cnt <= 0xffff)
|
||||
break;
|
||||
}
|
||||
@ -207,7 +211,8 @@ static int meson_pwm_calc(struct meson_pwm *meson,
|
||||
channel->lo = cnt;
|
||||
} else {
|
||||
/* Then check is we can have the duty with the same pre_div */
|
||||
duty_cnt = DIV_ROUND_CLOSEST(duty, fin_ns * (pre_div + 1));
|
||||
duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000,
|
||||
fin_ps * (pre_div + 1));
|
||||
if (duty_cnt > 0xffff) {
|
||||
dev_err(meson->chip.dev, "unable to get duty cycle\n");
|
||||
return -EINVAL;
|
||||
@ -381,6 +386,7 @@ static const char * const pwm_meson8b_parent_names[] = {
|
||||
|
||||
static const struct meson_pwm_data pwm_meson8b_data = {
|
||||
.parent_names = pwm_meson8b_parent_names,
|
||||
.num_parents = ARRAY_SIZE(pwm_meson8b_parent_names),
|
||||
};
|
||||
|
||||
static const char * const pwm_gxbb_parent_names[] = {
|
||||
@ -389,11 +395,35 @@ static const char * const pwm_gxbb_parent_names[] = {
|
||||
|
||||
static const struct meson_pwm_data pwm_gxbb_data = {
|
||||
.parent_names = pwm_gxbb_parent_names,
|
||||
.num_parents = ARRAY_SIZE(pwm_gxbb_parent_names),
|
||||
};
|
||||
|
||||
/*
|
||||
* Only the 2 first inputs of the GXBB AO PWMs are valid
|
||||
* The last 2 are grounded
|
||||
*/
|
||||
static const char * const pwm_gxbb_ao_parent_names[] = {
|
||||
"xtal", "clk81"
|
||||
};
|
||||
|
||||
static const struct meson_pwm_data pwm_gxbb_ao_data = {
|
||||
.parent_names = pwm_gxbb_ao_parent_names,
|
||||
.num_parents = ARRAY_SIZE(pwm_gxbb_ao_parent_names),
|
||||
};
|
||||
|
||||
static const struct of_device_id meson_pwm_matches[] = {
|
||||
{ .compatible = "amlogic,meson8b-pwm", .data = &pwm_meson8b_data },
|
||||
{ .compatible = "amlogic,meson-gxbb-pwm", .data = &pwm_gxbb_data },
|
||||
{
|
||||
.compatible = "amlogic,meson8b-pwm",
|
||||
.data = &pwm_meson8b_data
|
||||
},
|
||||
{
|
||||
.compatible = "amlogic,meson-gxbb-pwm",
|
||||
.data = &pwm_gxbb_data
|
||||
},
|
||||
{
|
||||
.compatible = "amlogic,meson-gxbb-ao-pwm",
|
||||
.data = &pwm_gxbb_ao_data
|
||||
},
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, meson_pwm_matches);
|
||||
@ -417,7 +447,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson,
|
||||
init.ops = &clk_mux_ops;
|
||||
init.flags = CLK_IS_BASIC;
|
||||
init.parent_names = meson->data->parent_names;
|
||||
init.num_parents = 1 << MISC_CLK_SEL_WIDTH;
|
||||
init.num_parents = meson->data->num_parents;
|
||||
|
||||
channel->mux.reg = meson->base + REG_MISC_AB;
|
||||
channel->mux.shift = mux_reg_shifts[i];
|
||||
|
@ -8,8 +8,10 @@
|
||||
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
@ -44,6 +46,10 @@
|
||||
|
||||
#define PWM_DTY_MASK GENMASK(15, 0)
|
||||
|
||||
#define PWM_REG_PRD(reg) ((((reg) >> 16) & PWM_PRD_MASK) + 1)
|
||||
#define PWM_REG_DTY(reg) ((reg) & PWM_DTY_MASK)
|
||||
#define PWM_REG_PRESCAL(reg, chan) (((reg) >> ((chan) * PWMCH_OFFSET)) & PWM_PRESCAL_MASK)
|
||||
|
||||
#define BIT_CH(bit, chan) ((bit) << ((chan) * PWMCH_OFFSET))
|
||||
|
||||
static const u32 prescaler_table[] = {
|
||||
@ -77,6 +83,8 @@ struct sun4i_pwm_chip {
|
||||
void __iomem *base;
|
||||
spinlock_t ctrl_lock;
|
||||
const struct sun4i_pwm_data *data;
|
||||
unsigned long next_period[2];
|
||||
bool needs_delay[2];
|
||||
};
|
||||
|
||||
static inline struct sun4i_pwm_chip *to_sun4i_pwm_chip(struct pwm_chip *chip)
|
||||
@ -96,26 +104,65 @@ static inline void sun4i_pwm_writel(struct sun4i_pwm_chip *chip,
|
||||
writel(val, chip->base + offset);
|
||||
}
|
||||
|
||||
static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
int duty_ns, int period_ns)
|
||||
static void sun4i_pwm_get_state(struct pwm_chip *chip,
|
||||
struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
|
||||
u32 prd, dty, val, clk_gate;
|
||||
u64 clk_rate, tmp;
|
||||
u32 val;
|
||||
unsigned int prescaler;
|
||||
|
||||
clk_rate = clk_get_rate(sun4i_pwm->clk);
|
||||
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
|
||||
if ((val == PWM_PRESCAL_MASK) && sun4i_pwm->data->has_prescaler_bypass)
|
||||
prescaler = 1;
|
||||
else
|
||||
prescaler = prescaler_table[PWM_REG_PRESCAL(val, pwm->hwpwm)];
|
||||
|
||||
if (prescaler == 0)
|
||||
return;
|
||||
|
||||
if (val & BIT_CH(PWM_ACT_STATE, pwm->hwpwm))
|
||||
state->polarity = PWM_POLARITY_NORMAL;
|
||||
else
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
|
||||
if (val & BIT_CH(PWM_CLK_GATING | PWM_EN, pwm->hwpwm))
|
||||
state->enabled = true;
|
||||
else
|
||||
state->enabled = false;
|
||||
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CH_PRD(pwm->hwpwm));
|
||||
|
||||
tmp = prescaler * NSEC_PER_SEC * PWM_REG_DTY(val);
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
|
||||
tmp = prescaler * NSEC_PER_SEC * PWM_REG_PRD(val);
|
||||
state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
}
|
||||
|
||||
static int sun4i_pwm_calculate(struct sun4i_pwm_chip *sun4i_pwm,
|
||||
struct pwm_state *state,
|
||||
u32 *dty, u32 *prd, unsigned int *prsclr)
|
||||
{
|
||||
u64 clk_rate, div = 0;
|
||||
unsigned int prescaler = 0;
|
||||
int err;
|
||||
unsigned int pval, prescaler = 0;
|
||||
|
||||
clk_rate = clk_get_rate(sun4i_pwm->clk);
|
||||
|
||||
if (sun4i_pwm->data->has_prescaler_bypass) {
|
||||
/* First, test without any prescaler when available */
|
||||
prescaler = PWM_PRESCAL_MASK;
|
||||
pval = 1;
|
||||
/*
|
||||
* When not using any prescaler, the clock period in nanoseconds
|
||||
* is not an integer so round it half up instead of
|
||||
* truncating to get less surprising values.
|
||||
*/
|
||||
div = clk_rate * period_ns + NSEC_PER_SEC / 2;
|
||||
div = clk_rate * state->period + NSEC_PER_SEC / 2;
|
||||
do_div(div, NSEC_PER_SEC);
|
||||
if (div - 1 > PWM_PRD_MASK)
|
||||
prescaler = 0;
|
||||
@ -126,137 +173,141 @@ static int sun4i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
for (prescaler = 0; prescaler < PWM_PRESCAL_MASK; prescaler++) {
|
||||
if (!prescaler_table[prescaler])
|
||||
continue;
|
||||
pval = prescaler_table[prescaler];
|
||||
div = clk_rate;
|
||||
do_div(div, prescaler_table[prescaler]);
|
||||
div = div * period_ns;
|
||||
do_div(div, pval);
|
||||
div = div * state->period;
|
||||
do_div(div, NSEC_PER_SEC);
|
||||
if (div - 1 <= PWM_PRD_MASK)
|
||||
break;
|
||||
}
|
||||
|
||||
if (div - 1 > PWM_PRD_MASK) {
|
||||
dev_err(chip->dev, "period exceeds the maximum value\n");
|
||||
if (div - 1 > PWM_PRD_MASK)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*prd = div;
|
||||
div *= state->duty_cycle;
|
||||
do_div(div, state->period);
|
||||
*dty = div;
|
||||
*prsclr = prescaler;
|
||||
|
||||
div = (u64)pval * NSEC_PER_SEC * *prd;
|
||||
state->period = DIV_ROUND_CLOSEST_ULL(div, clk_rate);
|
||||
|
||||
div = (u64)pval * NSEC_PER_SEC * *dty;
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(div, clk_rate);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
|
||||
struct pwm_state cstate;
|
||||
u32 ctrl;
|
||||
int ret;
|
||||
unsigned int delay_us;
|
||||
unsigned long now;
|
||||
|
||||
pwm_get_state(pwm, &cstate);
|
||||
|
||||
if (!cstate.enabled) {
|
||||
ret = clk_prepare_enable(sun4i_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
prd = div;
|
||||
div *= duty_ns;
|
||||
do_div(div, period_ns);
|
||||
dty = div;
|
||||
|
||||
err = clk_prepare_enable(sun4i_pwm->clk);
|
||||
if (err) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
spin_lock(&sun4i_pwm->ctrl_lock);
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
|
||||
if (sun4i_pwm->data->has_rdy && (val & PWM_RDY(pwm->hwpwm))) {
|
||||
spin_unlock(&sun4i_pwm->ctrl_lock);
|
||||
clk_disable_unprepare(sun4i_pwm->clk);
|
||||
return -EBUSY;
|
||||
if ((cstate.period != state->period) ||
|
||||
(cstate.duty_cycle != state->duty_cycle)) {
|
||||
u32 period, duty, val;
|
||||
unsigned int prescaler;
|
||||
|
||||
ret = sun4i_pwm_calculate(sun4i_pwm, state,
|
||||
&duty, &period, &prescaler);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "period exceeds the maximum value\n");
|
||||
spin_unlock(&sun4i_pwm->ctrl_lock);
|
||||
if (!cstate.enabled)
|
||||
clk_disable_unprepare(sun4i_pwm->clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (PWM_REG_PRESCAL(ctrl, pwm->hwpwm) != prescaler) {
|
||||
/* Prescaler changed, the clock has to be gated */
|
||||
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
|
||||
|
||||
ctrl &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm);
|
||||
ctrl |= BIT_CH(prescaler, pwm->hwpwm);
|
||||
}
|
||||
|
||||
val = (duty & PWM_DTY_MASK) | PWM_PRD(period);
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm));
|
||||
sun4i_pwm->next_period[pwm->hwpwm] = jiffies +
|
||||
usecs_to_jiffies(cstate.period / 1000 + 1);
|
||||
sun4i_pwm->needs_delay[pwm->hwpwm] = true;
|
||||
}
|
||||
|
||||
clk_gate = val & BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
if (clk_gate) {
|
||||
val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
|
||||
}
|
||||
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
val &= ~BIT_CH(PWM_PRESCAL_MASK, pwm->hwpwm);
|
||||
val |= BIT_CH(prescaler, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
|
||||
|
||||
val = (dty & PWM_DTY_MASK) | PWM_PRD(prd);
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CH_PRD(pwm->hwpwm));
|
||||
|
||||
if (clk_gate) {
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
val |= clk_gate;
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
|
||||
}
|
||||
|
||||
spin_unlock(&sun4i_pwm->ctrl_lock);
|
||||
clk_disable_unprepare(sun4i_pwm->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
enum pwm_polarity polarity)
|
||||
{
|
||||
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(sun4i_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
spin_lock(&sun4i_pwm->ctrl_lock);
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
|
||||
if (polarity != PWM_POLARITY_NORMAL)
|
||||
val &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
|
||||
if (state->polarity != PWM_POLARITY_NORMAL)
|
||||
ctrl &= ~BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
|
||||
else
|
||||
val |= BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
|
||||
ctrl |= BIT_CH(PWM_ACT_STATE, pwm->hwpwm);
|
||||
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
|
||||
|
||||
spin_unlock(&sun4i_pwm->ctrl_lock);
|
||||
clk_disable_unprepare(sun4i_pwm->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
ret = clk_prepare_enable(sun4i_pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "failed to enable PWM clock\n");
|
||||
return ret;
|
||||
ctrl |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
if (state->enabled) {
|
||||
ctrl |= BIT_CH(PWM_EN, pwm->hwpwm);
|
||||
} else if (!sun4i_pwm->needs_delay[pwm->hwpwm]) {
|
||||
ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm);
|
||||
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
}
|
||||
|
||||
spin_lock(&sun4i_pwm->ctrl_lock);
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
val |= BIT_CH(PWM_EN, pwm->hwpwm);
|
||||
val |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
|
||||
sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
|
||||
|
||||
spin_unlock(&sun4i_pwm->ctrl_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (state->enabled)
|
||||
return 0;
|
||||
|
||||
static void sun4i_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct sun4i_pwm_chip *sun4i_pwm = to_sun4i_pwm_chip(chip);
|
||||
u32 val;
|
||||
if (!sun4i_pwm->needs_delay[pwm->hwpwm]) {
|
||||
clk_disable_unprepare(sun4i_pwm->clk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* We need a full period to elapse before disabling the channel. */
|
||||
now = jiffies;
|
||||
if (sun4i_pwm->needs_delay[pwm->hwpwm] &&
|
||||
time_before(now, sun4i_pwm->next_period[pwm->hwpwm])) {
|
||||
delay_us = jiffies_to_usecs(sun4i_pwm->next_period[pwm->hwpwm] -
|
||||
now);
|
||||
if ((delay_us / 500) > MAX_UDELAY_MS)
|
||||
msleep(delay_us / 1000 + 1);
|
||||
else
|
||||
usleep_range(delay_us, delay_us * 2);
|
||||
}
|
||||
sun4i_pwm->needs_delay[pwm->hwpwm] = false;
|
||||
|
||||
spin_lock(&sun4i_pwm->ctrl_lock);
|
||||
val = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
val &= ~BIT_CH(PWM_EN, pwm->hwpwm);
|
||||
val &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4i_pwm, val, PWM_CTRL_REG);
|
||||
ctrl = sun4i_pwm_readl(sun4i_pwm, PWM_CTRL_REG);
|
||||
ctrl &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
|
||||
ctrl &= ~BIT_CH(PWM_EN, pwm->hwpwm);
|
||||
sun4i_pwm_writel(sun4i_pwm, ctrl, PWM_CTRL_REG);
|
||||
spin_unlock(&sun4i_pwm->ctrl_lock);
|
||||
|
||||
clk_disable_unprepare(sun4i_pwm->clk);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct pwm_ops sun4i_pwm_ops = {
|
||||
.config = sun4i_pwm_config,
|
||||
.set_polarity = sun4i_pwm_set_polarity,
|
||||
.enable = sun4i_pwm_enable,
|
||||
.disable = sun4i_pwm_disable,
|
||||
.apply = sun4i_pwm_apply,
|
||||
.get_state = sun4i_pwm_get_state,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
@ -316,8 +367,7 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct sun4i_pwm_chip *pwm;
|
||||
struct resource *res;
|
||||
u32 val;
|
||||
int i, ret;
|
||||
int ret;
|
||||
const struct of_device_id *match;
|
||||
|
||||
match = of_match_device(sun4i_pwm_dt_ids, &pdev->dev);
|
||||
@ -353,24 +403,7 @@ static int sun4i_pwm_probe(struct platform_device *pdev)
|
||||
|
||||
platform_set_drvdata(pdev, pwm);
|
||||
|
||||
ret = clk_prepare_enable(pwm->clk);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to enable PWM clock\n");
|
||||
goto clk_error;
|
||||
}
|
||||
|
||||
val = sun4i_pwm_readl(pwm, PWM_CTRL_REG);
|
||||
for (i = 0; i < pwm->chip.npwm; i++)
|
||||
if (!(val & BIT_CH(PWM_ACT_STATE, i)))
|
||||
pwm_set_polarity(&pwm->chip.pwms[i],
|
||||
PWM_POLARITY_INVERSED);
|
||||
clk_disable_unprepare(pwm->clk);
|
||||
|
||||
return 0;
|
||||
|
||||
clk_error:
|
||||
pwmchip_remove(&pwm->chip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sun4i_pwm_remove(struct platform_device *pdev)
|
||||
|
@ -41,6 +41,9 @@
|
||||
|
||||
struct tegra_pwm_soc {
|
||||
unsigned int num_channels;
|
||||
|
||||
/* Maximum IP frequency for given SoCs */
|
||||
unsigned long max_frequency;
|
||||
};
|
||||
|
||||
struct tegra_pwm_chip {
|
||||
@ -201,7 +204,18 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
||||
if (IS_ERR(pwm->clk))
|
||||
return PTR_ERR(pwm->clk);
|
||||
|
||||
/* Read PWM clock rate from source */
|
||||
/* Set maximum frequency of the IP */
|
||||
ret = clk_set_rate(pwm->clk, pwm->soc->max_frequency);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to set max frequency: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* The requested and configured frequency may differ due to
|
||||
* clock register resolutions. Get the configured frequency
|
||||
* so that PWM period can be calculated more accurately.
|
||||
*/
|
||||
pwm->clk_rate = clk_get_rate(pwm->clk);
|
||||
|
||||
pwm->rst = devm_reset_control_get(&pdev->dev, "pwm");
|
||||
@ -273,10 +287,12 @@ static int tegra_pwm_resume(struct device *dev)
|
||||
|
||||
static const struct tegra_pwm_soc tegra20_pwm_soc = {
|
||||
.num_channels = 4,
|
||||
.max_frequency = 48000000UL,
|
||||
};
|
||||
|
||||
static const struct tegra_pwm_soc tegra186_pwm_soc = {
|
||||
.num_channels = 1,
|
||||
.max_frequency = 102000000UL,
|
||||
};
|
||||
|
||||
static const struct of_device_id tegra_pwm_of_match[] = {
|
||||
|
Loading…
Reference in New Issue
Block a user