pwm: Changes for v5.8-rc1
Nothing too exciting for this cycle. A couple of fixes across the board, and Lee volunteered to help with patch review. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAl7jhQUZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zoZeGD/4r/owv45JI0iQU05zl9JQi hl4nQQcQqJYIZ2VraEKkpaZ509NYMr1y4wypxRIoezjsVPCbMpBr96Mb+J6IYU1h JV+qIqQgLw7qThjCPs7CltjZUEPjRiU5kyWD3nut5YRUo3V55WzbolYnZrV9UDcu gQ/PTehQ4ujdqENnwjhUlvbtjvCXnMreAHPPiBHzHJ+YesKAvIWLG645EdFpCEIZ hS4/PndU2WwMVcsyYzmVlKfB1bUjGwxGpqD1kSobf+CDxXLv8b9/L+L2eAU/O1om VnzHiGjsu+cnEWQmBV/A9Zwb10QfMiP7sEseFiy7mywqOZCX8GHxcUOhg9eJmXZb 1A4PXAHHhgQayuAnR7u9w5XuC8hMypltPPaCfTdWkc5awBeZ3bgJYGYCR1OAs/7q aoHxtrwpvBlUCGSkBC5WSZdsf1XGBmy3Q0fZr232xKiUxBPeAnkVQS6bjYS7tOUh 1xJrCFKR/BkFs0E4P8zyqRuRieh9GfwnKTw4dHO4QCFYEugXq/VYB/pUaofKoUdz gdFv5Pw73f2RjRK1Kdtc8lBnUa7lulsfP3ewjKdgO+Ob/w0w4o1VN6aJkC6SkHNk aWhhZipFZ4POUWFJVQkRHiTi88UIbMNVPabNlVWZvW6T9+uUKL7bdELbJXxrzzaK sLuKDgpNtSGfn8wu2un0sg== =QI2J -----END PGP SIGNATURE----- Merge tag 'pwm/for-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm updates from Thierry Reding: "Nothing too exciting for this cycle. A couple of fixes across the board, and Lee volunteered to help with patch review" * tag 'pwm/for-5.8-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: pwm: Add missing "CONFIG_" prefix MAINTAINERS: Add Lee Jones as reviewer for the PWM subsystem pwm: imx27: Fix rounding behavior pwm: rockchip: Simplify rockchip_pwm_get_state() pwm: img: Call pm_runtime_put() in pm_runtime_get_sync() failed case pwm: tegra: Support dynamic clock frequency configuration pwm: jz4740: Add support for the JZ4725B pwm: jz4740: Make PWM start with the active part pwm: jz4740: Enhance precision in calculation of duty cycle pwm: jz4740: Drop dependency on MACH_INGENIC pwm: lpss: Fix get_state runtime-pm reference handling pwm: sun4i: Support direct clock output on Allwinner A64 pwm: Add support for Azoteq IQS620A PWM generator dt-bindings: pwm: rcar: add r8a77961 support pwm: Add missing '\n' in log messages
This commit is contained in:
Коммит
9433a51ec1
|
@ -27,6 +27,7 @@ properties:
|
|||
- renesas,pwm-r8a7794 # R-Car E2
|
||||
- renesas,pwm-r8a7795 # R-Car H3
|
||||
- renesas,pwm-r8a7796 # R-Car M3-W
|
||||
- renesas,pwm-r8a77961 # R-Car M3-W+
|
||||
- renesas,pwm-r8a77965 # R-Car M3-N
|
||||
- renesas,pwm-r8a77970 # R-Car V3M
|
||||
- renesas,pwm-r8a77980 # R-Car V3H
|
||||
|
|
|
@ -13919,6 +13919,7 @@ F: drivers/media/rc/pwm-ir-tx.c
|
|||
PWM SUBSYSTEM
|
||||
M: Thierry Reding <thierry.reding@gmail.com>
|
||||
R: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
|
||||
M: Lee Jones <lee.jones@linaro.org>
|
||||
L: linux-pwm@vger.kernel.org
|
||||
S: Maintained
|
||||
Q: https://patchwork.ozlabs.org/project/linux-pwm/list/
|
||||
|
|
|
@ -232,9 +232,19 @@ config PWM_IMX_TPM
|
|||
To compile this driver as a module, choose M here: the module
|
||||
will be called pwm-imx-tpm.
|
||||
|
||||
config PWM_IQS620A
|
||||
tristate "Azoteq IQS620A PWM support"
|
||||
depends on MFD_IQS62X || COMPILE_TEST
|
||||
help
|
||||
Generic PWM framework driver for the Azoteq IQS620A multi-function
|
||||
sensor.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called pwm-iqs620a.
|
||||
|
||||
config PWM_JZ4740
|
||||
tristate "Ingenic JZ47xx PWM support"
|
||||
depends on MACH_INGENIC
|
||||
depends on MIPS
|
||||
depends on COMMON_CLK
|
||||
select MFD_SYSCON
|
||||
help
|
||||
|
|
|
@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_IMG) += pwm-img.o
|
|||
obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o
|
||||
obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o
|
||||
obj-$(CONFIG_PWM_IMX_TPM) += pwm-imx-tpm.o
|
||||
obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o
|
||||
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
|
||||
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
|
||||
obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o
|
||||
|
|
|
@ -121,7 +121,7 @@ static int pwm_device_request(struct pwm_device *pwm, const char *label)
|
|||
pwm->chip->ops->get_state(pwm->chip, pwm, &pwm->state);
|
||||
trace_pwm_get(pwm, &pwm->state);
|
||||
|
||||
if (IS_ENABLED(PWM_DEBUG))
|
||||
if (IS_ENABLED(CONFIG_PWM_DEBUG))
|
||||
pwm->last = pwm->state;
|
||||
}
|
||||
|
||||
|
@ -537,7 +537,7 @@ static void pwm_apply_state_debug(struct pwm_device *pwm,
|
|||
|
||||
if (!state->enabled && s2.enabled && s2.duty_cycle > 0)
|
||||
dev_warn(chip->dev,
|
||||
"requested disabled, but yielded enabled with duty > 0");
|
||||
"requested disabled, but yielded enabled with duty > 0\n");
|
||||
|
||||
/* reapply the state that the driver reported being configured. */
|
||||
err = chip->ops->apply(chip, pwm, &s1);
|
||||
|
|
|
@ -129,8 +129,10 @@ static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
duty = DIV_ROUND_UP(timebase * duty_ns, period_ns);
|
||||
|
||||
ret = pm_runtime_get_sync(chip->dev);
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_autosuspend(chip->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
|
||||
val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm));
|
||||
|
@ -331,8 +333,10 @@ static int img_pwm_remove(struct platform_device *pdev)
|
|||
int ret;
|
||||
|
||||
ret = pm_runtime_get_sync(&pdev->dev);
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
pm_runtime_put(&pdev->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < pwm_chip->chip.npwm; i++) {
|
||||
val = img_pwm_readl(pwm_chip, PWM_CTRL_CFG);
|
||||
|
|
|
@ -150,13 +150,12 @@ static void pwm_imx27_get_state(struct pwm_chip *chip,
|
|||
|
||||
prescaler = MX3_PWMCR_PRESCALER_GET(val);
|
||||
pwm_clk = clk_get_rate(imx->clk_per);
|
||||
pwm_clk = DIV_ROUND_CLOSEST_ULL(pwm_clk, prescaler);
|
||||
val = readl(imx->mmio_base + MX3_PWMPR);
|
||||
period = val >= MX3_PWMPR_MAX ? MX3_PWMPR_MAX : val;
|
||||
|
||||
/* PWMOUT (Hz) = PWMCLK / (PWMPR + 2) */
|
||||
tmp = NSEC_PER_SEC * (u64)(period + 2);
|
||||
state->period = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk);
|
||||
tmp = NSEC_PER_SEC * (u64)(period + 2) * prescaler;
|
||||
state->period = DIV_ROUND_UP_ULL(tmp, pwm_clk);
|
||||
|
||||
/*
|
||||
* PWMSAR can be read only if PWM is enabled. If the PWM is disabled,
|
||||
|
@ -167,8 +166,8 @@ static void pwm_imx27_get_state(struct pwm_chip *chip,
|
|||
else
|
||||
val = imx->duty_cycle;
|
||||
|
||||
tmp = NSEC_PER_SEC * (u64)(val);
|
||||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, pwm_clk);
|
||||
tmp = NSEC_PER_SEC * (u64)(val) * prescaler;
|
||||
state->duty_cycle = DIV_ROUND_UP_ULL(tmp, pwm_clk);
|
||||
|
||||
pwm_imx27_clk_disable_unprepare(imx);
|
||||
}
|
||||
|
@ -220,22 +219,23 @@ static int pwm_imx27_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
struct pwm_imx27_chip *imx = to_pwm_imx27_chip(chip);
|
||||
struct pwm_state cstate;
|
||||
unsigned long long c;
|
||||
unsigned long long clkrate;
|
||||
int ret;
|
||||
u32 cr;
|
||||
|
||||
pwm_get_state(pwm, &cstate);
|
||||
|
||||
c = clk_get_rate(imx->clk_per);
|
||||
c *= state->period;
|
||||
clkrate = clk_get_rate(imx->clk_per);
|
||||
c = clkrate * state->period;
|
||||
|
||||
do_div(c, 1000000000);
|
||||
do_div(c, NSEC_PER_SEC);
|
||||
period_cycles = c;
|
||||
|
||||
prescale = period_cycles / 0x10000 + 1;
|
||||
|
||||
period_cycles /= prescale;
|
||||
c = (unsigned long long)period_cycles * state->duty_cycle;
|
||||
do_div(c, state->period);
|
||||
c = clkrate * state->duty_cycle;
|
||||
do_div(c, NSEC_PER_SEC * prescale);
|
||||
duty_cycles = c;
|
||||
|
||||
/*
|
||||
|
|
|
@ -0,0 +1,270 @@
|
|||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Azoteq IQS620A PWM Generator
|
||||
*
|
||||
* Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>
|
||||
*
|
||||
* Limitations:
|
||||
* - The period is fixed to 1 ms and is generated continuously despite changes
|
||||
* to the duty cycle or enable/disable state.
|
||||
* - Changes to the duty cycle or enable/disable state take effect immediately
|
||||
* and may result in a glitch during the period in which the change is made.
|
||||
* - The device cannot generate a 0% duty cycle. For duty cycles below 1 / 256
|
||||
* ms, the output is disabled and relies upon an external pull-down resistor
|
||||
* to hold the GPIO3/LTX pin low.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mfd/iqs62x.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/notifier.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define IQS620_PWR_SETTINGS 0xD2
|
||||
#define IQS620_PWR_SETTINGS_PWM_OUT BIT(7)
|
||||
|
||||
#define IQS620_PWM_DUTY_CYCLE 0xD8
|
||||
|
||||
#define IQS620_PWM_PERIOD_NS 1000000
|
||||
|
||||
struct iqs620_pwm_private {
|
||||
struct iqs62x_core *iqs62x;
|
||||
struct pwm_chip chip;
|
||||
struct notifier_block notifier;
|
||||
struct mutex lock;
|
||||
bool out_en;
|
||||
u8 duty_val;
|
||||
};
|
||||
|
||||
static int iqs620_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
struct iqs620_pwm_private *iqs620_pwm;
|
||||
struct iqs62x_core *iqs62x;
|
||||
int duty_scale, ret;
|
||||
|
||||
if (state->polarity != PWM_POLARITY_NORMAL)
|
||||
return -ENOTSUPP;
|
||||
|
||||
if (state->period < IQS620_PWM_PERIOD_NS)
|
||||
return -EINVAL;
|
||||
|
||||
iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
|
||||
iqs62x = iqs620_pwm->iqs62x;
|
||||
|
||||
/*
|
||||
* The duty cycle generated by the device is calculated as follows:
|
||||
*
|
||||
* duty_cycle = (IQS620_PWM_DUTY_CYCLE + 1) / 256 * 1 ms
|
||||
*
|
||||
* ...where IQS620_PWM_DUTY_CYCLE is a register value between 0 and 255
|
||||
* (inclusive). Therefore the lowest duty cycle the device can generate
|
||||
* while the output is enabled is 1 / 256 ms.
|
||||
*
|
||||
* For lower duty cycles (e.g. 0), the PWM output is simply disabled to
|
||||
* allow an external pull-down resistor to hold the GPIO3/LTX pin low.
|
||||
*/
|
||||
duty_scale = state->duty_cycle * 256 / IQS620_PWM_PERIOD_NS;
|
||||
|
||||
mutex_lock(&iqs620_pwm->lock);
|
||||
|
||||
if (!state->enabled || !duty_scale) {
|
||||
ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,
|
||||
IQS620_PWR_SETTINGS_PWM_OUT, 0);
|
||||
if (ret)
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
if (duty_scale) {
|
||||
u8 duty_val = min(duty_scale - 1, 0xFF);
|
||||
|
||||
ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE,
|
||||
duty_val);
|
||||
if (ret)
|
||||
goto err_mutex;
|
||||
|
||||
iqs620_pwm->duty_val = duty_val;
|
||||
}
|
||||
|
||||
if (state->enabled && duty_scale) {
|
||||
ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,
|
||||
IQS620_PWR_SETTINGS_PWM_OUT, 0xFF);
|
||||
if (ret)
|
||||
goto err_mutex;
|
||||
}
|
||||
|
||||
iqs620_pwm->out_en = state->enabled;
|
||||
|
||||
err_mutex:
|
||||
mutex_unlock(&iqs620_pwm->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void iqs620_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct iqs620_pwm_private *iqs620_pwm;
|
||||
|
||||
iqs620_pwm = container_of(chip, struct iqs620_pwm_private, chip);
|
||||
|
||||
mutex_lock(&iqs620_pwm->lock);
|
||||
|
||||
/*
|
||||
* Since the device cannot generate a 0% duty cycle, requests to do so
|
||||
* cause subsequent calls to iqs620_pwm_get_state to report the output
|
||||
* as disabled with duty cycle equal to that which was in use prior to
|
||||
* the request. This is not ideal, but is the best compromise based on
|
||||
* the capabilities of the device.
|
||||
*/
|
||||
state->enabled = iqs620_pwm->out_en;
|
||||
state->duty_cycle = DIV_ROUND_UP((iqs620_pwm->duty_val + 1) *
|
||||
IQS620_PWM_PERIOD_NS, 256);
|
||||
|
||||
mutex_unlock(&iqs620_pwm->lock);
|
||||
|
||||
state->period = IQS620_PWM_PERIOD_NS;
|
||||
}
|
||||
|
||||
static int iqs620_pwm_notifier(struct notifier_block *notifier,
|
||||
unsigned long event_flags, void *context)
|
||||
{
|
||||
struct iqs620_pwm_private *iqs620_pwm;
|
||||
struct iqs62x_core *iqs62x;
|
||||
int ret;
|
||||
|
||||
if (!(event_flags & BIT(IQS62X_EVENT_SYS_RESET)))
|
||||
return NOTIFY_DONE;
|
||||
|
||||
iqs620_pwm = container_of(notifier, struct iqs620_pwm_private,
|
||||
notifier);
|
||||
iqs62x = iqs620_pwm->iqs62x;
|
||||
|
||||
mutex_lock(&iqs620_pwm->lock);
|
||||
|
||||
/*
|
||||
* The parent MFD driver already prints an error message in the event
|
||||
* of a device reset, so nothing else is printed here unless there is
|
||||
* an additional failure.
|
||||
*/
|
||||
ret = regmap_write(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE,
|
||||
iqs620_pwm->duty_val);
|
||||
if (ret)
|
||||
goto err_mutex;
|
||||
|
||||
ret = regmap_update_bits(iqs62x->regmap, IQS620_PWR_SETTINGS,
|
||||
IQS620_PWR_SETTINGS_PWM_OUT,
|
||||
iqs620_pwm->out_en ? 0xFF : 0);
|
||||
|
||||
err_mutex:
|
||||
mutex_unlock(&iqs620_pwm->lock);
|
||||
|
||||
if (ret) {
|
||||
dev_err(iqs620_pwm->chip.dev,
|
||||
"Failed to re-initialize device: %d\n", ret);
|
||||
return NOTIFY_BAD;
|
||||
}
|
||||
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
static const struct pwm_ops iqs620_pwm_ops = {
|
||||
.apply = iqs620_pwm_apply,
|
||||
.get_state = iqs620_pwm_get_state,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static void iqs620_pwm_notifier_unregister(void *context)
|
||||
{
|
||||
struct iqs620_pwm_private *iqs620_pwm = context;
|
||||
int ret;
|
||||
|
||||
ret = blocking_notifier_chain_unregister(&iqs620_pwm->iqs62x->nh,
|
||||
&iqs620_pwm->notifier);
|
||||
if (ret)
|
||||
dev_err(iqs620_pwm->chip.dev,
|
||||
"Failed to unregister notifier: %d\n", ret);
|
||||
}
|
||||
|
||||
static int iqs620_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);
|
||||
struct iqs620_pwm_private *iqs620_pwm;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
iqs620_pwm = devm_kzalloc(&pdev->dev, sizeof(*iqs620_pwm), GFP_KERNEL);
|
||||
if (!iqs620_pwm)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(pdev, iqs620_pwm);
|
||||
iqs620_pwm->iqs62x = iqs62x;
|
||||
|
||||
ret = regmap_read(iqs62x->regmap, IQS620_PWR_SETTINGS, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
iqs620_pwm->out_en = val & IQS620_PWR_SETTINGS_PWM_OUT;
|
||||
|
||||
ret = regmap_read(iqs62x->regmap, IQS620_PWM_DUTY_CYCLE, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
iqs620_pwm->duty_val = val;
|
||||
|
||||
iqs620_pwm->chip.dev = &pdev->dev;
|
||||
iqs620_pwm->chip.ops = &iqs620_pwm_ops;
|
||||
iqs620_pwm->chip.base = -1;
|
||||
iqs620_pwm->chip.npwm = 1;
|
||||
|
||||
mutex_init(&iqs620_pwm->lock);
|
||||
|
||||
iqs620_pwm->notifier.notifier_call = iqs620_pwm_notifier;
|
||||
ret = blocking_notifier_chain_register(&iqs620_pwm->iqs62x->nh,
|
||||
&iqs620_pwm->notifier);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_add_action_or_reset(&pdev->dev,
|
||||
iqs620_pwm_notifier_unregister,
|
||||
iqs620_pwm);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pwmchip_add(&iqs620_pwm->chip);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to add device: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int iqs620_pwm_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct iqs620_pwm_private *iqs620_pwm = platform_get_drvdata(pdev);
|
||||
int ret;
|
||||
|
||||
ret = pwmchip_remove(&iqs620_pwm->chip);
|
||||
if (ret)
|
||||
dev_err(&pdev->dev, "Failed to remove device: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver iqs620_pwm_platform_driver = {
|
||||
.driver = {
|
||||
.name = "iqs620a-pwm",
|
||||
},
|
||||
.probe = iqs620_pwm_probe,
|
||||
.remove = iqs620_pwm_remove,
|
||||
};
|
||||
module_platform_driver(iqs620_pwm_platform_driver);
|
||||
|
||||
MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");
|
||||
MODULE_DESCRIPTION("Azoteq IQS620A PWM Generator");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:iqs620a-pwm");
|
|
@ -6,7 +6,6 @@
|
|||
* Limitations:
|
||||
* - The .apply callback doesn't complete the currently running period before
|
||||
* reconfiguring the hardware.
|
||||
* - Each period starts with the inactive part.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
@ -21,7 +20,9 @@
|
|||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#define NUM_PWM 8
|
||||
struct soc_info {
|
||||
unsigned int num_pwms;
|
||||
};
|
||||
|
||||
struct jz4740_pwm_chip {
|
||||
struct pwm_chip chip;
|
||||
|
@ -37,7 +38,7 @@ static bool jz4740_pwm_can_use_chn(struct jz4740_pwm_chip *jz,
|
|||
unsigned int channel)
|
||||
{
|
||||
/* Enable all TCU channels for PWM use by default except channels 0/1 */
|
||||
u32 pwm_channels_mask = GENMASK(NUM_PWM - 1, 2);
|
||||
u32 pwm_channels_mask = GENMASK(jz->chip.npwm - 1, 2);
|
||||
|
||||
device_property_read_u32(jz->chip.dev->parent,
|
||||
"ingenic,pwm-channels-mask",
|
||||
|
@ -158,12 +159,12 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
/* Calculate period value */
|
||||
tmp = (unsigned long long)rate * state->period;
|
||||
do_div(tmp, NSEC_PER_SEC);
|
||||
period = (unsigned long)tmp;
|
||||
period = tmp;
|
||||
|
||||
/* Calculate duty value */
|
||||
tmp = (unsigned long long)period * state->duty_cycle;
|
||||
do_div(tmp, state->period);
|
||||
duty = period - tmp;
|
||||
tmp = (unsigned long long)rate * state->duty_cycle;
|
||||
do_div(tmp, NSEC_PER_SEC);
|
||||
duty = tmp;
|
||||
|
||||
if (duty >= period)
|
||||
duty = period - 1;
|
||||
|
@ -189,18 +190,26 @@ static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
|
||||
TCU_TCSR_PWM_SD, TCU_TCSR_PWM_SD);
|
||||
|
||||
/* Set polarity */
|
||||
switch (state->polarity) {
|
||||
case PWM_POLARITY_NORMAL:
|
||||
/*
|
||||
* Set polarity.
|
||||
*
|
||||
* The PWM starts in inactive state until the internal timer reaches the
|
||||
* duty value, then becomes active until the timer reaches the period
|
||||
* value. In theory, we should then use (period - duty) as the real duty
|
||||
* value, as a high duty value would otherwise result in the PWM pin
|
||||
* being inactive most of the time.
|
||||
*
|
||||
* Here, we don't do that, and instead invert the polarity of the PWM
|
||||
* when it is active. This trick makes the PWM start with its active
|
||||
* state instead of its inactive state.
|
||||
*/
|
||||
if ((state->polarity == PWM_POLARITY_NORMAL) ^ state->enabled)
|
||||
regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
|
||||
TCU_TCSR_PWM_INITL_HIGH, 0);
|
||||
break;
|
||||
case PWM_POLARITY_INVERSED:
|
||||
else
|
||||
regmap_update_bits(jz4740->map, TCU_REG_TCSRc(pwm->hwpwm),
|
||||
TCU_TCSR_PWM_INITL_HIGH,
|
||||
TCU_TCSR_PWM_INITL_HIGH);
|
||||
break;
|
||||
}
|
||||
|
||||
if (state->enabled)
|
||||
jz4740_pwm_enable(chip, pwm);
|
||||
|
@ -219,6 +228,11 @@ static int jz4740_pwm_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct jz4740_pwm_chip *jz4740;
|
||||
const struct soc_info *info;
|
||||
|
||||
info = device_get_match_data(dev);
|
||||
if (!info)
|
||||
return -EINVAL;
|
||||
|
||||
jz4740 = devm_kzalloc(dev, sizeof(*jz4740), GFP_KERNEL);
|
||||
if (!jz4740)
|
||||
|
@ -232,7 +246,7 @@ static int jz4740_pwm_probe(struct platform_device *pdev)
|
|||
|
||||
jz4740->chip.dev = dev;
|
||||
jz4740->chip.ops = &jz4740_pwm_ops;
|
||||
jz4740->chip.npwm = NUM_PWM;
|
||||
jz4740->chip.npwm = info->num_pwms;
|
||||
jz4740->chip.base = -1;
|
||||
jz4740->chip.of_xlate = of_pwm_xlate_with_flags;
|
||||
jz4740->chip.of_pwm_n_cells = 3;
|
||||
|
@ -249,9 +263,18 @@ static int jz4740_pwm_remove(struct platform_device *pdev)
|
|||
return pwmchip_remove(&jz4740->chip);
|
||||
}
|
||||
|
||||
static const struct soc_info __maybe_unused jz4740_soc_info = {
|
||||
.num_pwms = 8,
|
||||
};
|
||||
|
||||
static const struct soc_info __maybe_unused jz4725b_soc_info = {
|
||||
.num_pwms = 6,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static const struct of_device_id jz4740_pwm_dt_ids[] = {
|
||||
{ .compatible = "ingenic,jz4740-pwm", },
|
||||
{ .compatible = "ingenic,jz4740-pwm", .data = &jz4740_soc_info },
|
||||
{ .compatible = "ingenic,jz4725b-pwm", .data = &jz4725b_soc_info },
|
||||
{},
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids);
|
||||
|
|
|
@ -158,7 +158,6 @@ static int pwm_lpss_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* This function gets called once from pwmchip_add to get the initial state */
|
||||
static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
|
@ -167,6 +166,8 @@ static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
unsigned long long base_unit, freq, on_time_div;
|
||||
u32 ctrl;
|
||||
|
||||
pm_runtime_get_sync(chip->dev);
|
||||
|
||||
base_unit_range = BIT(lpwm->info->base_unit_bits);
|
||||
|
||||
ctrl = pwm_lpss_read(pwm);
|
||||
|
@ -187,8 +188,7 @@ static void pwm_lpss_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
state->polarity = PWM_POLARITY_NORMAL;
|
||||
state->enabled = !!(ctrl & PWM_ENABLE);
|
||||
|
||||
if (state->enabled)
|
||||
pm_runtime_get(chip->dev);
|
||||
pm_runtime_put(chip->dev);
|
||||
}
|
||||
|
||||
static const struct pwm_ops pwm_lpss_ops = {
|
||||
|
@ -202,7 +202,8 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
|
|||
{
|
||||
struct pwm_lpss_chip *lpwm;
|
||||
unsigned long c;
|
||||
int ret;
|
||||
int i, ret;
|
||||
u32 ctrl;
|
||||
|
||||
if (WARN_ON(info->npwm > MAX_PWMS))
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
@ -232,6 +233,12 @@ struct pwm_lpss_chip *pwm_lpss_probe(struct device *dev, struct resource *r,
|
|||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
for (i = 0; i < lpwm->info->npwm; i++) {
|
||||
ctrl = pwm_lpss_read(&lpwm->chip.pwms[i]);
|
||||
if (ctrl & PWM_ENABLE)
|
||||
pm_runtime_get(dev);
|
||||
}
|
||||
|
||||
return lpwm;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pwm_lpss_probe);
|
||||
|
|
|
@ -83,12 +83,7 @@ static void rockchip_pwm_get_state(struct pwm_chip *chip,
|
|||
state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
|
||||
|
||||
val = readl_relaxed(pc->base + pc->data->regs.ctrl);
|
||||
if (pc->data->supports_polarity)
|
||||
state->enabled = ((val & enable_conf) != enable_conf) ?
|
||||
false : true;
|
||||
else
|
||||
state->enabled = ((val & enable_conf) == enable_conf) ?
|
||||
true : false;
|
||||
state->enabled = (val & enable_conf) == enable_conf;
|
||||
|
||||
if (pc->data->supports_polarity && !(val & PWM_DUTY_POSITIVE))
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
|
|
|
@ -352,6 +352,12 @@ static const struct sun4i_pwm_data sun4i_pwm_single_bypass = {
|
|||
.npwm = 1,
|
||||
};
|
||||
|
||||
static const struct sun4i_pwm_data sun50i_a64_pwm_data = {
|
||||
.has_prescaler_bypass = true,
|
||||
.has_direct_mod_clk_output = true,
|
||||
.npwm = 1,
|
||||
};
|
||||
|
||||
static const struct sun4i_pwm_data sun50i_h6_pwm_data = {
|
||||
.has_prescaler_bypass = true,
|
||||
.has_direct_mod_clk_output = true,
|
||||
|
@ -374,6 +380,9 @@ static const struct of_device_id sun4i_pwm_dt_ids[] = {
|
|||
}, {
|
||||
.compatible = "allwinner,sun8i-h3-pwm",
|
||||
.data = &sun4i_pwm_single_bypass,
|
||||
}, {
|
||||
.compatible = "allwinner,sun50i-a64-pwm",
|
||||
.data = &sun50i_a64_pwm_data,
|
||||
}, {
|
||||
.compatible = "allwinner,sun50i-h6-pwm",
|
||||
.data = &sun50i_h6_pwm_data,
|
||||
|
|
|
@ -4,8 +4,36 @@
|
|||
*
|
||||
* Tegra pulse-width-modulation controller driver
|
||||
*
|
||||
* Copyright (c) 2010, NVIDIA Corporation.
|
||||
* Copyright (c) 2010-2020, NVIDIA Corporation.
|
||||
* Based on arch/arm/plat-mxc/pwm.c by Sascha Hauer <s.hauer@pengutronix.de>
|
||||
*
|
||||
* Overview of Tegra Pulse Width Modulator Register:
|
||||
* 1. 13-bit: Frequency division (SCALE)
|
||||
* 2. 8-bit : Pulse division (DUTY)
|
||||
* 3. 1-bit : Enable bit
|
||||
*
|
||||
* The PWM clock frequency is divided by 256 before subdividing it based
|
||||
* on the programmable frequency division value to generate the required
|
||||
* frequency for PWM output. The maximum output frequency that can be
|
||||
* achieved is (max rate of source clock) / 256.
|
||||
* e.g. if source clock rate is 408 MHz, maximum output frequency can be:
|
||||
* 408 MHz/256 = 1.6 MHz.
|
||||
* This 1.6 MHz frequency can further be divided using SCALE value in PWM.
|
||||
*
|
||||
* PWM pulse width: 8 bits are usable [23:16] for varying pulse width.
|
||||
* To achieve 100% duty cycle, program Bit [24] of this register to
|
||||
* 1’b1. In which case the other bits [23:16] are set to don't care.
|
||||
*
|
||||
* Limitations:
|
||||
* - When PWM is disabled, the output is driven to inactive.
|
||||
* - It does not allow the current PWM period to complete and
|
||||
* stops abruptly.
|
||||
*
|
||||
* - If the register is reconfigured while PWM is running,
|
||||
* it does not complete the currently running period.
|
||||
*
|
||||
* - If the user input duty is beyond acceptible limits,
|
||||
* -EINVAL is returned.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
|
@ -41,6 +69,7 @@ struct tegra_pwm_chip {
|
|||
struct reset_control*rst;
|
||||
|
||||
unsigned long clk_rate;
|
||||
unsigned long min_period_ns;
|
||||
|
||||
void __iomem *regs;
|
||||
|
||||
|
@ -68,7 +97,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
{
|
||||
struct tegra_pwm_chip *pc = to_tegra_pwm_chip(chip);
|
||||
unsigned long long c = duty_ns, hz;
|
||||
unsigned long rate;
|
||||
unsigned long rate, required_clk_rate;
|
||||
u32 val = 0;
|
||||
int err;
|
||||
|
||||
|
@ -82,10 +111,48 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
val = (u32)c << PWM_DUTY_SHIFT;
|
||||
|
||||
/*
|
||||
* min period = max clock limit >> PWM_DUTY_WIDTH
|
||||
*/
|
||||
if (period_ns < pc->min_period_ns)
|
||||
return -EINVAL;
|
||||
|
||||
/*
|
||||
* Compute the prescaler value for which (1 << PWM_DUTY_WIDTH)
|
||||
* cycles at the PWM clock rate will take period_ns nanoseconds.
|
||||
*
|
||||
* num_channels: If single instance of PWM controller has multiple
|
||||
* channels (e.g. Tegra210 or older) then it is not possible to
|
||||
* configure separate clock rates to each of the channels, in such
|
||||
* case the value stored during probe will be referred.
|
||||
*
|
||||
* If every PWM controller instance has one channel respectively, i.e.
|
||||
* nums_channels == 1 then only the clock rate can be modified
|
||||
* dynamically (e.g. Tegra186 or Tegra194).
|
||||
*/
|
||||
if (pc->soc->num_channels == 1) {
|
||||
/*
|
||||
* Rate is multiplied with 2^PWM_DUTY_WIDTH so that it matches
|
||||
* with the maximum possible rate that the controller can
|
||||
* provide. Any further lower value can be derived by setting
|
||||
* PFM bits[0:12].
|
||||
*
|
||||
* required_clk_rate is a reference rate for source clock and
|
||||
* it is derived based on user requested period. By setting the
|
||||
* source clock rate as required_clk_rate, PWM controller will
|
||||
* be able to configure the requested period.
|
||||
*/
|
||||
required_clk_rate =
|
||||
(NSEC_PER_SEC / period_ns) << PWM_DUTY_WIDTH;
|
||||
|
||||
err = clk_set_rate(pc->clk, required_clk_rate);
|
||||
if (err < 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* Store the new rate for further references */
|
||||
pc->clk_rate = clk_get_rate(pc->clk);
|
||||
}
|
||||
|
||||
rate = pc->clk_rate >> PWM_DUTY_WIDTH;
|
||||
|
||||
/* Consider precision in PWM_SCALE_WIDTH rate calculation */
|
||||
|
@ -94,7 +161,7 @@ static int tegra_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
|
|||
|
||||
/*
|
||||
* Since the actual PWM divider is the register's frequency divider
|
||||
* field minus 1, we need to decrement to get the correct value to
|
||||
* field plus 1, we need to decrement to get the correct value to
|
||||
* write to the register.
|
||||
*/
|
||||
if (rate > 0)
|
||||
|
@ -205,6 +272,10 @@ static int tegra_pwm_probe(struct platform_device *pdev)
|
|||
*/
|
||||
pwm->clk_rate = clk_get_rate(pwm->clk);
|
||||
|
||||
/* Set minimum limit of PWM period for the IP */
|
||||
pwm->min_period_ns =
|
||||
(NSEC_PER_SEC / (pwm->soc->max_frequency >> PWM_DUTY_WIDTH)) + 1;
|
||||
|
||||
pwm->rst = devm_reset_control_get_exclusive(&pdev->dev, "pwm");
|
||||
if (IS_ERR(pwm->rst)) {
|
||||
ret = PTR_ERR(pwm->rst);
|
||||
|
@ -312,5 +383,6 @@ static struct platform_driver tegra_pwm_driver = {
|
|||
module_platform_driver(tegra_pwm_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("NVIDIA Corporation");
|
||||
MODULE_AUTHOR("Sandipan Patra <spatra@nvidia.com>");
|
||||
MODULE_DESCRIPTION("Tegra PWM controller driver");
|
||||
MODULE_ALIAS("platform:tegra-pwm");
|
||||
|
|
Загрузка…
Ссылка в новой задаче