diff options
author | Jae Hyun Yoo <jae.hyun.yoo@intel.com> | 2019-02-12 04:02:35 +0300 |
---|---|---|
committer | Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> | 2021-11-05 10:22:07 +0300 |
commit | f5b63d083feb8183c81a9db6ef5152401797a60a (patch) | |
tree | 496025d01321bf143e6b2cf48e0ff445f46a9272 | |
parent | 26db00c31eaf0f99b080df0607b94ea7c9383513 (diff) | |
download | linux-f5b63d083feb8183c81a9db6ef5152401797a60a.tar.xz |
Add Aspeed PWM driver which uses FTTMR010 timer IP
This commit adds Aspeed PWM driver which uses timer pulse output
feature in Aspeed SoCs. The timer IP is derived from Faraday
Technologies FTTMR010 IP but has some customized register
structure changes only for Aspeed SoCs.
Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@intel.com>
-rw-r--r-- | arch/arm/boot/dts/aspeed-g5.dtsi | 2 | ||||
-rw-r--r-- | drivers/pwm/Kconfig | 9 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-fttmr010.c | 437 |
4 files changed, 448 insertions, 1 deletions
diff --git a/arch/arm/boot/dts/aspeed-g5.dtsi b/arch/arm/boot/dts/aspeed-g5.dtsi index fb648d7d5a11..f2d8de61bfc2 100644 --- a/arch/arm/boot/dts/aspeed-g5.dtsi +++ b/arch/arm/boot/dts/aspeed-g5.dtsi @@ -366,7 +366,7 @@ timer: timer@1e782000 { /* This timer is a Faraday FTTMR010 derivative */ - compatible = "aspeed,ast2400-timer"; + compatible = "aspeed,ast2500-timer"; reg = <0x1e782000 0x90>; interrupts = <16 17 18 35 36 37 38 39>; clocks = <&syscon ASPEED_CLK_APB>; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index aa29841bbb79..4ad30dcda4f9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -195,6 +195,15 @@ config PWM_FSL_FTM To compile this driver as a module, choose M here: the module will be called pwm-fsl-ftm. +config PWM_FTTMR010 + tristate "Faraday Technology FTTMR010 timer PWM support" + help + Generic PWM framework driver for Faraday Technology FTTMR010 Timer + PWM output + + To compile this driver as a module, choose M here: the module + will be called pwm-fttmr010 + config PWM_HIBVT tristate "HiSilicon BVT PWM support" depends on ARCH_HISI || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 708840b7fba8..12605f055bc2 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -16,6 +16,7 @@ obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o obj-$(CONFIG_PWM_DWC) += pwm-dwc.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o +obj-$(CONFIG_PWM_FTTMR010) += pwm-fttmr010.o obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o diff --git a/drivers/pwm/pwm-fttmr010.c b/drivers/pwm/pwm-fttmr010.c new file mode 100644 index 000000000000..2c1b2d59d05f --- /dev/null +++ b/drivers/pwm/pwm-fttmr010.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2019 Intel Corporation + +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> + +#define TIMER_CR 0x30 + +#define TIMER5_ASPEED_COUNT 0x50 +#define TIMER5_ASPEED_LOAD 0x54 +#define TIMER5_ASPEED_MATCH1 0x58 +#define TIMER5_ASPEED_MATCH2 0x5c +#define TIMER6_ASPEED_COUNT 0x60 +#define TIMER6_ASPEED_LOAD 0x64 +#define TIMER6_ASPEED_MATCH1 0x68 +#define TIMER6_ASPEED_MATCH2 0x6c +#define TIMER7_ASPEED_COUNT 0x70 +#define TIMER7_ASPEED_LOAD 0x74 +#define TIMER7_ASPEED_MATCH1 0x78 +#define TIMER7_ASPEED_MATCH2 0x7c +#define TIMER8_ASPEED_COUNT 0x80 +#define TIMER8_ASPEED_LOAD 0x84 +#define TIMER8_ASPEED_MATCH1 0x88 +#define TIMER8_ASPEED_MATCH2 0x8c + +#define TIMER_5_CR_ASPEED_ENABLE BIT(16) +#define TIMER_5_CR_ASPEED_CLOCK BIT(17) +#define TIMER_5_CR_ASPEED_INT BIT(18) +#define TIMER_5_CR_ASPEED_PULSE_OUT BIT(19) +#define TIMER_6_CR_ASPEED_ENABLE BIT(20) +#define TIMER_6_CR_ASPEED_CLOCK BIT(21) +#define TIMER_6_CR_ASPEED_INT BIT(22) +#define TIMER_6_CR_ASPEED_PULSE_OUT BIT(23) +#define TIMER_7_CR_ASPEED_ENABLE BIT(24) +#define TIMER_7_CR_ASPEED_CLOCK BIT(25) +#define TIMER_7_CR_ASPEED_INT BIT(26) +#define TIMER_7_CR_ASPEED_PULSE_OUT BIT(27) +#define TIMER_8_CR_ASPEED_ENABLE BIT(28) +#define TIMER_8_CR_ASPEED_CLOCK BIT(29) +#define TIMER_8_CR_ASPEED_INT BIT(30) +#define TIMER_8_CR_ASPEED_PULSE_OUT BIT(31) + +/** + * struct pwm_fttmr010_variant - variant data depends on SoC + * @bits: timer counter resolution + * @chan_min: lowest timer channel which has pwm pulse output + * @chan_max: highest timer channel which has pwm pulse output + * @output_mask: pwm pulse output mask which is defined in device tree + */ +struct pwm_fttmr010_variant { + u8 bits; + u8 chan_min; + u8 chan_max; + u8 output_mask; +}; + +/** + * struct pwm_fttmr010_chan - private data of FTTMR010 PWM channel + * @period_ns: current period in nanoseconds programmed to the hardware + * @duty_ns: current duty time in nanoseconds programmed to the hardware + */ +struct pwm_fttmr010_chan { + u32 period_ns; + u32 duty_ns; +}; + +/** + * struct pwm_fttmr010 - private data of FTTMR010 PWM + * @chip: generic PWM chip + * @variant: local copy of hardware variant data + * @disabled_mask: disabled status for all channels - one bit per channel + * @base: base address of mapped PWM registers + * @clk: clock used to drive the timers + */ +struct pwm_fttmr010 { + struct pwm_chip chip; + struct pwm_fttmr010_variant variant; + u8 disabled_mask; + void __iomem *base; + struct clk *clk; + u32 clk_tick_ns; +}; + +static inline +struct pwm_fttmr010 *to_pwm_fttmr010(struct pwm_chip *chip) +{ + return container_of(chip, struct pwm_fttmr010, chip); +} + +static int pwm_fttmr010_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); + struct pwm_fttmr010_chan *chan; + + if (!(priv->variant.output_mask & BIT(pwm->hwpwm))) { + dev_warn(chip->dev, + "tried to request PWM channel %d without output\n", + pwm->hwpwm); + return -EINVAL; + } + + chan = devm_kzalloc(chip->dev, sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + pwm_set_chip_data(pwm, chan); + + return 0; +} + +static void pwm_fttmr010_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + devm_kfree(chip->dev, pwm_get_chip_data(pwm)); + pwm_set_chip_data(pwm, NULL); +} + +static int pwm_fttmr010_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); + u32 cr; + + cr = readl(priv->base + TIMER_CR); + + switch (pwm->hwpwm) { + case 5: + cr |= (TIMER_5_CR_ASPEED_ENABLE | TIMER_5_CR_ASPEED_PULSE_OUT); + break; + case 6: + cr |= (TIMER_6_CR_ASPEED_ENABLE | TIMER_6_CR_ASPEED_PULSE_OUT); + break; + case 7: + cr |= (TIMER_7_CR_ASPEED_ENABLE | TIMER_7_CR_ASPEED_PULSE_OUT); + break; + case 8: + cr |= (TIMER_8_CR_ASPEED_ENABLE | TIMER_8_CR_ASPEED_PULSE_OUT); + break; + default: + return -ERANGE; + } + + writel(cr, priv->base + TIMER_CR); + priv->disabled_mask &= ~BIT(pwm->hwpwm); + + return 0; +} + +static void pwm_fttmr010_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); + u32 cr; + + cr = readl(priv->base + TIMER_CR); + + switch (pwm->hwpwm) { + case 5: + cr &= ~(TIMER_5_CR_ASPEED_ENABLE | TIMER_5_CR_ASPEED_PULSE_OUT); + break; + case 6: + cr &= ~(TIMER_6_CR_ASPEED_ENABLE | TIMER_6_CR_ASPEED_PULSE_OUT); + break; + case 7: + cr &= ~(TIMER_7_CR_ASPEED_ENABLE | TIMER_7_CR_ASPEED_PULSE_OUT); + break; + case 8: + cr &= ~(TIMER_8_CR_ASPEED_ENABLE | TIMER_8_CR_ASPEED_PULSE_OUT); + break; + default: + return; + } + + writel(cr, priv->base + TIMER_CR); + priv->disabled_mask |= BIT(pwm->hwpwm); +} + +static int pwm_fttmr010_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + u32 tload, tmatch, creg_offset, lreg_offset, mreg_offset; + struct pwm_fttmr010_chan *chan = pwm_get_chip_data(pwm); + struct pwm_fttmr010 *priv = to_pwm_fttmr010(chip); + + /* + * We currently avoid using 64bit arithmetic by using the + * fact that anything faster than 1Hz is easily representable + * by 32bits. + */ + if (period_ns > NSEC_PER_SEC) + return -ERANGE; + + /* No need to update */ + if (chan->period_ns == period_ns || chan->duty_ns == duty_ns) + return 0; + + tload = period_ns / priv->clk_tick_ns; + + /* Period is too short */ + if (tload <= 1) + return -ERANGE; + + tmatch = duty_ns / priv->clk_tick_ns; + + /* 0% duty is not available */ + if (!tmatch) + ++tmatch; + + tmatch = tload - tmatch; + + /* Decrement to get tick numbers, instead of tick counts */ + --tload; + --tmatch; + + if (tload == 0 || tmatch == 0) + return -ERANGE; + + dev_dbg(priv->chip.dev, "clk_tick_ns:%u, tload:%u, tmatch:%u\n", + priv->clk_tick_ns, tload, tmatch); + + switch (pwm->hwpwm) { + case 5: + creg_offset = TIMER5_ASPEED_COUNT; + lreg_offset = TIMER5_ASPEED_LOAD; + mreg_offset = TIMER5_ASPEED_MATCH1; + break; + case 6: + creg_offset = TIMER6_ASPEED_COUNT; + lreg_offset = TIMER6_ASPEED_LOAD; + mreg_offset = TIMER6_ASPEED_MATCH1; + break; + case 7: + creg_offset = TIMER7_ASPEED_COUNT; + lreg_offset = TIMER7_ASPEED_LOAD; + mreg_offset = TIMER7_ASPEED_MATCH1; + break; + case 8: + creg_offset = TIMER8_ASPEED_COUNT; + lreg_offset = TIMER8_ASPEED_LOAD; + mreg_offset = TIMER8_ASPEED_MATCH1; + break; + default: + return -ERANGE; + } + + writel(tload, priv->base + creg_offset); + writel(tload, priv->base + lreg_offset); + writel(tmatch, priv->base + mreg_offset); + + chan->period_ns = period_ns; + chan->duty_ns = duty_ns; + + return 0; +} + +static const struct pwm_ops pwm_fttmr010_ops = { + .request = pwm_fttmr010_request, + .free = pwm_fttmr010_free, + .enable = pwm_fttmr010_enable, + .disable = pwm_fttmr010_disable, + .config = pwm_fttmr010_config, + .owner = THIS_MODULE, +}; + +#ifdef CONFIG_OF +static const struct pwm_fttmr010_variant aspeed_variant = { + .bits = 32, + .chan_min = 5, + .chan_max = 8, +}; + +static const struct of_device_id pwm_fttmr010_matches[] = { + { .compatible = "aspeed,ast2400-timer", .data = &aspeed_variant }, + { .compatible = "aspeed,ast2500-timer", .data = &aspeed_variant }, + { }, +}; +MODULE_DEVICE_TABLE(of, pwm_fttmr010_matches); + +static int pwm_fttmr010_parse_dt(struct pwm_fttmr010 *priv) +{ + struct device_node *np = priv->chip.dev->of_node; + const struct of_device_id *match; + struct property *prop; + const __be32 *cur; + u32 val; + + match = of_match_node(pwm_fttmr010_matches, np); + if (!match) + return -ENODEV; + + memcpy(&priv->variant, match->data, sizeof(priv->variant)); + + of_property_for_each_u32(np, "fttmr010,pwm-outputs", prop, cur, val) { + if (val < priv->variant.chan_min || + val > priv->variant.chan_max) { + dev_err(priv->chip.dev, + "invalid channel index in fttmr010,pwm-outputs property\n"); + continue; + } + priv->variant.output_mask |= BIT(val); + } + + return 0; +} +#else +static int pwm_fttmr010_parse_dt(struct pwm_fttmr010 *priv) +{ + return -ENODEV; +} +#endif + +static int pwm_fttmr010_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_fttmr010 *priv; + struct resource *res; + ulong clk_rate; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->chip.dev = &pdev->dev; + priv->chip.ops = &pwm_fttmr010_ops; + priv->chip.base = -1; + + if (IS_ENABLED(CONFIG_OF) && pdev->dev.of_node) { + ret = pwm_fttmr010_parse_dt(priv); + if (ret) + return ret; + + priv->chip.of_xlate = of_pwm_xlate_with_flags; + priv->chip.of_pwm_n_cells = 3; + } else { + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data specified\n"); + return -EINVAL; + } + + memcpy(&priv->variant, pdev->dev.platform_data, + sizeof(priv->variant)); + } + + priv->chip.npwm = priv->variant.chan_max + 1; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + priv->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->clk = devm_clk_get(&pdev->dev, "PCLK"); + if (IS_ERR(priv->clk)) { + dev_err(dev, "failed to get timer base clk\n"); + return PTR_ERR(priv->clk); + } + + ret = clk_prepare_enable(priv->clk); + if (ret < 0) { + dev_err(dev, "failed to enable base clock\n"); + return ret; + } + + clk_rate = clk_get_rate(priv->clk); + priv->clk_tick_ns = NSEC_PER_SEC / clk_rate; + + platform_set_drvdata(pdev, priv); + + ret = pwmchip_add(&priv->chip); + if (ret < 0) { + dev_err(dev, "failed to register PWM chip\n"); + clk_disable_unprepare(priv->clk); + return ret; + } + + dev_dbg(dev, "clk at %lu\n", clk_rate); + + return 0; +} + +static int pwm_fttmr010_remove(struct platform_device *pdev) +{ + struct pwm_fttmr010 *priv = platform_get_drvdata(pdev); + + pwmchip_remove(&priv->chip); + clk_disable_unprepare(priv->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pwm_fttmr010_resume(struct device *dev) +{ + struct pwm_fttmr010 *priv = dev_get_drvdata(dev); + struct pwm_chip *chip = &priv->chip; + unsigned int i; + + for (i = priv->variant.chan_min; i < priv->variant.chan_max; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + struct pwm_fttmr010_chan *chan = pwm_get_chip_data(pwm); + + if (!chan) + continue; + + if (chan->period_ns) { + pwm_fttmr010_config(chip, pwm, chan->duty_ns, + chan->period_ns); + } + + if (priv->disabled_mask & BIT(i)) + pwm_fttmr010_disable(chip, pwm); + else + pwm_fttmr010_enable(chip, pwm); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pwm_fttmr010_pm_ops, NULL, pwm_fttmr010_resume); + +static struct platform_driver pwm_fttmr010_driver = { + .driver = { + .name = "fttmr010-timer-pwm", + .pm = &pwm_fttmr010_pm_ops, + .of_match_table = of_match_ptr(pwm_fttmr010_matches), + }, + .probe = pwm_fttmr010_probe, + .remove = pwm_fttmr010_remove, +}; +module_platform_driver(pwm_fttmr010_driver); + +MODULE_AUTHOR("Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com>"); +MODULE_DESCRIPTION("FTTMR010 PWM Driver for timer pulse outputs"); +MODULE_LICENSE("GPL v2"); |