mirror of
https://github.com/torvalds/linux.git
synced 2024-11-25 05:32:00 +00:00
6e2bbb688a
This set of changes contains a new driver for SiFive SoCs as well as enhancements to the core (device links are used to track dependencies between PWM providers and consumers, support for PWM controllers via ACPI, sysfs will now suspend/resume PWMs that it has claimed) and various existing drivers. -----BEGIN PGP SIGNATURE----- iQJNBAABCAA3FiEEiOrDCAFJzPfAjcif3SOs138+s6EFAl0V/lAZHHRoaWVycnku cmVkaW5nQGdtYWlsLmNvbQAKCRDdI6zXfz6zoS+uD/0cJqcVhX1c2S/pHg1k4QFh wREnEbxMqWghcsSZcO0gk0hoRyxMNBM3iOldaKc3b5LVtEJOv/R7W6RB+FMcvPKA AtW/ydyfRZiqL9bIXs0hhaW4Fo0WCq6gZksDU5cOoq4KMHfkEp7D7U158ItsEtga ufDigs8fv/Z6c5DaEfoJ10I+VCy/We2YnCdIVZuL/MElFHlUupzRpGZv6uMRQ4WI z2/SEtHURoW103a3UrEmjqv0GeoHPrHwEP9kZTUuakyMxPmUtrSUJRybi79Cf27B jLYql8bXSkTsV6rUBtTRNtqQjD3hdjcFYaEdOle8n52/pYFohycmVvB/3xvr9tDC Wildg4Rniv4lcteB1hqB0M5km/szXGjPx5wozvmctwOia5sogG+8DWGp0fZO8Gsp vaF+GbTrM4LV1AzGJW7icTRFQG7VFUcZAglNW4o82hcXN1j9GpQ/qSOY3vgBigx+ vyWrbCHBH2zjJNh1sSl68zi5q90T9IlXFfgR61kujbHYws+KrO3BJE2SW7qsLhsf HJnMBBxpoxvusBS/kbsWsDCnoGi4UsCeKUbmbfY1OjpCNlpp+cHSk6b4134Fmi66 D8B+a4C1I/CNhcV72P+hAdrva4UXB6oJi4hZDE2/tEioXQB2wJO4AwWzjpifqzBY nGxZVPV7TuXj2KwCXDQnvw== =nseo -----END PGP SIGNATURE----- Merge tag 'pwm/for-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm Pull pwm updates from Thierry Reding: "This set of changes contains a new driver for SiFive SoCs as well as enhancements to the core (device links are used to track dependencies between PWM providers and consumers, support for PWM controllers via ACPI, sysfs will now suspend/resume PWMs that it has claimed) and various existing drivers" * tag 'pwm/for-5.3-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm: (37 commits) pwm: fsl-ftm: Make sure to unlock mutex on failure pwm: fsl-ftm: Use write protection for prescaler & polarity pwm: fsl-ftm: More relaxed permissions for updating period pwm: atmel-hlcdc: Add compatible for SAM9X60 HLCDC's PWM pwm: bcm2835: Improve precision of PWM leds: pwm: Support ACPI via firmware-node framework pwm: Add support referencing PWMs from ACPI pwm: rcar: Remove suspend/resume support pwm: sysfs: Add suspend/resume support pwm: Add power management descriptions pwm: meson: Add documentation to the driver pwm: meson: Add support PWM_POLARITY_INVERSED when disabling pwm: meson: Don't cache struct pwm_state internally pwm: meson: Read the full hardware state in meson_pwm_get_state() pwm: meson: Simplify the calculation of the pre-divider and count pwm: meson: Move pwm_set_chip_data() to meson_pwm_request() pwm: meson: Add the per-channel register offsets and bits in a struct pwm: meson: Add the meson_pwm_channel data to struct meson_pwm pwm: meson: Pass struct pwm_device to meson_pwm_calc() pwm: meson: Don't duplicate the polarity internally ...
201 lines
4.6 KiB
C
201 lines
4.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
|
|
* JZ4740 platform PWM support
|
|
*/
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/err.h>
|
|
#include <linux/gpio.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/pwm.h>
|
|
|
|
#include <asm/mach-jz4740/timer.h>
|
|
|
|
#define NUM_PWM 8
|
|
|
|
struct jz4740_pwm_chip {
|
|
struct pwm_chip chip;
|
|
struct clk *clk;
|
|
};
|
|
|
|
static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip)
|
|
{
|
|
return container_of(chip, struct jz4740_pwm_chip, chip);
|
|
}
|
|
|
|
static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
/*
|
|
* Timers 0 and 1 are used for system tasks, so they are unavailable
|
|
* for use as PWMs.
|
|
*/
|
|
if (pwm->hwpwm < 2)
|
|
return -EBUSY;
|
|
|
|
jz4740_timer_start(pwm->hwpwm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
jz4740_timer_set_ctrl(pwm->hwpwm, 0);
|
|
|
|
jz4740_timer_stop(pwm->hwpwm);
|
|
}
|
|
|
|
static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm);
|
|
|
|
ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
|
|
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
|
jz4740_timer_enable(pwm->hwpwm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|
{
|
|
uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm);
|
|
|
|
/*
|
|
* Set duty > period. This trick allows the TCU channels in TCU2 mode to
|
|
* properly return to their init level.
|
|
*/
|
|
jz4740_timer_set_duty(pwm->hwpwm, 0xffff);
|
|
jz4740_timer_set_period(pwm->hwpwm, 0x0);
|
|
|
|
/*
|
|
* Disable PWM output.
|
|
* In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the
|
|
* counter is stopped, while in TCU1 mode the order does not matter.
|
|
*/
|
|
ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
|
|
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
|
|
|
/* Stop counter */
|
|
jz4740_timer_disable(pwm->hwpwm);
|
|
}
|
|
|
|
static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
|
struct pwm_state *state)
|
|
{
|
|
struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip);
|
|
unsigned long long tmp;
|
|
unsigned long period, duty;
|
|
unsigned int prescaler = 0;
|
|
uint16_t ctrl;
|
|
|
|
tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period;
|
|
do_div(tmp, 1000000000);
|
|
period = tmp;
|
|
|
|
while (period > 0xffff && prescaler < 6) {
|
|
period >>= 2;
|
|
++prescaler;
|
|
}
|
|
|
|
if (prescaler == 6)
|
|
return -EINVAL;
|
|
|
|
tmp = (unsigned long long)period * state->duty_cycle;
|
|
do_div(tmp, state->period);
|
|
duty = period - tmp;
|
|
|
|
if (duty >= period)
|
|
duty = period - 1;
|
|
|
|
jz4740_pwm_disable(chip, pwm);
|
|
|
|
jz4740_timer_set_count(pwm->hwpwm, 0);
|
|
jz4740_timer_set_duty(pwm->hwpwm, duty);
|
|
jz4740_timer_set_period(pwm->hwpwm, period);
|
|
|
|
ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
|
|
JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
|
|
|
|
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
|
|
|
switch (state->polarity) {
|
|
case PWM_POLARITY_NORMAL:
|
|
ctrl &= ~JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
|
|
break;
|
|
case PWM_POLARITY_INVERSED:
|
|
ctrl |= JZ_TIMER_CTRL_PWM_ACTIVE_LOW;
|
|
break;
|
|
}
|
|
|
|
jz4740_timer_set_ctrl(pwm->hwpwm, ctrl);
|
|
|
|
if (state->enabled)
|
|
jz4740_pwm_enable(chip, pwm);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct pwm_ops jz4740_pwm_ops = {
|
|
.request = jz4740_pwm_request,
|
|
.free = jz4740_pwm_free,
|
|
.apply = jz4740_pwm_apply,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static int jz4740_pwm_probe(struct platform_device *pdev)
|
|
{
|
|
struct jz4740_pwm_chip *jz4740;
|
|
|
|
jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL);
|
|
if (!jz4740)
|
|
return -ENOMEM;
|
|
|
|
jz4740->clk = devm_clk_get(&pdev->dev, "ext");
|
|
if (IS_ERR(jz4740->clk))
|
|
return PTR_ERR(jz4740->clk);
|
|
|
|
jz4740->chip.dev = &pdev->dev;
|
|
jz4740->chip.ops = &jz4740_pwm_ops;
|
|
jz4740->chip.npwm = NUM_PWM;
|
|
jz4740->chip.base = -1;
|
|
jz4740->chip.of_xlate = of_pwm_xlate_with_flags;
|
|
jz4740->chip.of_pwm_n_cells = 3;
|
|
|
|
platform_set_drvdata(pdev, jz4740);
|
|
|
|
return pwmchip_add(&jz4740->chip);
|
|
}
|
|
|
|
static int jz4740_pwm_remove(struct platform_device *pdev)
|
|
{
|
|
struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev);
|
|
|
|
return pwmchip_remove(&jz4740->chip);
|
|
}
|
|
|
|
#ifdef CONFIG_OF
|
|
static const struct of_device_id jz4740_pwm_dt_ids[] = {
|
|
{ .compatible = "ingenic,jz4740-pwm", },
|
|
{},
|
|
};
|
|
MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids);
|
|
#endif
|
|
|
|
static struct platform_driver jz4740_pwm_driver = {
|
|
.driver = {
|
|
.name = "jz4740-pwm",
|
|
.of_match_table = of_match_ptr(jz4740_pwm_dt_ids),
|
|
},
|
|
.probe = jz4740_pwm_probe,
|
|
.remove = jz4740_pwm_remove,
|
|
};
|
|
module_platform_driver(jz4740_pwm_driver);
|
|
|
|
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
|
|
MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver");
|
|
MODULE_ALIAS("platform:jz4740-pwm");
|
|
MODULE_LICENSE("GPL");
|