pwm: loongson1: Add PWM driver for Loongson1 SoC
Subsystems (3)
PATCH 1/2 pwm: loongson1: Add PWM driver for Loongson1 SoC
Add support for the PWM controller present in Loongson1 family of SoCs.
--- drivers/pwm/Kconfig | 9 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-loongson1.c | 169 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 drivers/pwm/pwm-loongson1.c
drivers/pwm/Kconfig +9 -0
@@ -216,6 +216,15 @@ config PWM_JZ4740 To compile this driver as a module, choose M here: the module will be called pwm-jz4740. +config PWM_LOONGSON1 + tristate "Loongson1 PWM support" + depends on MACH_LOONGSON32 + help + Generic PWM framework driver for Loongson1 based machines. + + To compile this driver as a module, choose M here: the module + will be called pwm-loongson1. + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943
drivers/pwm/Makefile +1 -0
@@ -19,6 +19,7 @@ obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o +obj-$(CONFIG_PWM_LOONGSON1) += pwm-loongson1.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o
drivers/pwm/pwm-loongson1.c new file +169 -0
169 lines not on this page · raw message
Signature · 1 line
1.9.1
Hi Yang,
Quoted 87 lines · Yang Ling
Add support for the PWM controller present in Loongson1 family of SoCs.
Signed-off-by Yang Ling <[email protected]>--- drivers/pwm/Kconfig | 9 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-loongson1.c | 169 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 drivers/pwm/pwm-loongson1.cdrivers/pwm/Kconfig +9
-0@@ -216,6 +216,15 @@ config PWM_JZ4740 To compile this driver as a module, choose M here: the module will be called pwm-jz4740. +config PWM_LOONGSON1 + tristate "Loongson1 PWM support" + depends on MACH_LOONGSON32 + help + Generic PWM framework driver for Loongson1 based machines. + + To compile this driver as a module, choose M here: the module + will be called pwm-loongson1. + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943drivers/pwm/Makefile +1
-0@@ -19,6 +19,7 @@ obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o +obj-$(CONFIG_PWM_LOONGSON1) += pwm-loongson1.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.odrivers/pwm/pwm-loongson1.c new file +39
-0@@ -0,0 +1,169 @@ +/* +* Copyright (c) 2017 Yang Ling <[email protected]> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <loongson1.h> + +struct ls1x_pwm_chip { +struct clk *clk; +void __iomem *base; +struct pwm_chip chip; +}; + +struct ls1x_pwm_channel { +u32 period_ns; +u32 duty_ns; +}; +static inline struct ls1x_pwm_chip *to_ls1x_pwm_chip (struct pwm_chip *chip) +{ + return container_of(chip, struct ls1x_pwm_chip, chip); +} + +static int ls1x_pwm_request (struct pwm_chip *chip,struct pwm_device *pwm) +{ +struct ls1x_pwm_channel *chan =NULL ; + + chan =devm_kzalloc (chip->dev,sizeof (*chan),GFP_KERNEL ); +if (!chan) +return -ENOMEM ; + +pwm_set_chip_data (pwm, chan);
drivers/pwm/pwm-loongson1.c line 49
Quoted 10 lines
drivers/pwm/pwm-loongson1.c new file +10
-0+ +return 0 ; +} + +static void ls1x_pwm_free (struct pwm_chip *chip,struct pwm_device *pwm) +{ +devm_kfree (chip->dev,pwm_get_chip_data (pwm)); +pwm_set_chip_data (pwm,NULL ); +} +
Period and duty are stored in the pwm_device already, so you're just duplicating the same data here. If you remove ls1x_pwm_channel then all of the code above becomes unnecessary ...
drivers/pwm/pwm-loongson1.c line 61
Quoted 11 lines
drivers/pwm/pwm-loongson1.c new file +11
-0+static int ls1x_pwm_config (struct pwm_chip *chip,struct pwm_device *pwm, +int duty_ns,int period_ns) +{ +struct ls1x_pwm_chip *pc =to_ls1x_pwm_chip (chip); +struct ls1x_pwm_channel *chan =pwm_get_chip_data (pwm); +unsigned long long tmp; +unsigned long period, duty; + +if (period_ns == chan->period_ns && duty_ns == chan->duty_ns) +return 0 ; +
above check is already handled in pwm core
Quoted 11 lines
drivers/pwm/pwm-loongson1.c new file +11
-0+ tmp = (unsigned long long )clk_get_rate (pc->clk) * period_ns; +do_div (tmp,1000000000 ); + period = tmp; + + tmp = (unsigned long long )period * duty_ns; +do_div (tmp, period_ns); + duty = period - tmp; + +if (duty >= period) + duty = period -1 ; +
drivers/pwm/pwm-loongson1.c line 82
Quoted 10 lines
drivers/pwm/pwm-loongson1.c new file +10
-0+if (duty >>24 || period >>24 ) +return -EINVAL ; + + chan->period_ns = period_ns; + chan->duty_ns = duty_ns; + +writel (duty, pc->base +PWM_HRC (pwm->hwpwm)); +writel (period, pc->base +PWM_LRC (pwm->hwpwm)); +writel (0x00 , pc->base +PWM_CNT (pwm->hwpwm)); +
PWM_HRC and PWM_LRC names suggest that you're using high/low state counters here rather than duty/period - but with no documentation I'm just guessing here.
Quoted 39 lines
drivers/pwm/pwm-loongson1.c new file +39
-0+return 0 ; +} + +static int ls1x_pwm_enable (struct pwm_chip *chip,struct pwm_device *pwm) +{ +struct ls1x_pwm_chip *pc =to_ls1x_pwm_chip (chip); + +writel (CNT_RST , pc->base +PWM_CTRL (pwm->hwpwm)); +writel (CNT_EN , pc->base +PWM_CTRL (pwm->hwpwm)); + +return 0 ; +} + +static void ls1x_pwm_disable (struct pwm_chip *chip,struct pwm_device *pwm) +{ +struct ls1x_pwm_chip *pc =to_ls1x_pwm_chip (chip); + +writel (PWM_OE , pc->base +PWM_CTRL (pwm->hwpwm)); +} + +static const struct pwm_ops ls1x_pwm_ops = { + .request = ls1x_pwm_request, + .free = ls1x_pwm_free, + .config = ls1x_pwm_config, + .enable = ls1x_pwm_enable, + .disable = ls1x_pwm_disable, + .owner =THIS_MODULE , +}; + +static int ls1x_pwm_probe (struct platform_device *pdev) +{ +struct ls1x_pwm_chip *pc =NULL ; +struct resource *res =NULL ; + + pc =devm_kzalloc (&pdev->dev,sizeof (*pc),GFP_KERNEL ); +if (!pc) +return -ENOMEM ; + + res =platform_get_resource (pdev,IORESOURCE_MEM ,0 );
drivers/pwm/pwm-loongson1.c line 131
Quoted 10 lines
drivers/pwm/pwm-loongson1.c new file +10
-0+ pc->base =devm_ioremap_resource (&pdev->dev, res); +if (IS_ERR (pc->base)) +return PTR_ERR (pc->base); + + pc->clk =devm_clk_get (&pdev->dev,"ls1x-pwmtimer" ); +if (IS_ERR (pc->clk)) { +dev_err (&pdev->dev,"failed to get %s clock\n" , pdev->name); +return PTR_ERR (pc->clk); + } +clk_prepare_enable (pc->clk);
Should check for errors here
drivers/pwm/pwm-loongson1.c line 140
Quoted 8 lines
drivers/pwm/pwm-loongson1.c new file +8
-0+ pc->chip.ops = &ls1x_pwm_ops; + pc->chip.dev = &pdev->dev; + pc->chip.base =-1 ; + pc->chip.npwm =4 ; + +platform_set_drvdata (pdev, pc); + +return pwmchip_add (&pc->chip);
Should check for errors (and disable clk appropriately)
Quoted 20 lines
drivers/pwm/pwm-loongson1.c new file +19
-0+} + +static int ls1x_pwm_remove (struct platform_device *pdev) +{ +struct ls1x_pwm_chip *pc =platform_get_drvdata (pdev); +int ret; + + ret =pwmchip_remove (&pc->chip); +if (ret <0 ) +return ret; + +clk_disable_unprepare (pc->clk); + +return 0 ; +} + +static struct platform_driver ls1x_pwm_driver = { + .driver = { + .name ="ls1x-pwm" ,
drivers/pwm/pwm-loongson1.c line 169
Quoted 10 lines
drivers/pwm/pwm-loongson1.c new file +10
-0+ }, + .probe = ls1x_pwm_probe, + .remove = ls1x_pwm_remove, +}; +module_platform_driver (ls1x_pwm_driver); + +MODULE_AUTHOR ("Yang Ling <[email protected]>" ); +MODULE_DESCRIPTION ("Loongson1 PWM driver" ); +MODULE_ALIAS ("platform:loongson1-pwm" ); +MODULE_LICENSE ("GPL" );
Best regards,
Marcin
Hi Marcin,
Quoted 97 lines · Marcin Nowakowski
Hi Yang,
Quoted 93 lines · Yang Ling
Add support for the PWM controller present in Loongson1 family of SoCs.
Signed-off-by Yang Ling <[email protected]>---
drivers/pwm/Kconfig | 9 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-loongson1.c | 169 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+)create mode 100644
drivers/pwm/pwm-loongson1.cdrivers/pwm/Kconfig +9
-0@@ -216,6 +216,15 @@ config PWM_JZ4740 To compile this driver as a module, choose M here: the module will be called pwm-jz4740. +config PWM_LOONGSON1 + tristate "Loongson1 PWM support" + depends on MACH_LOONGSON32 + help + Generic PWM framework driver for Loongson1 based machines. + + To compile this driver as a module, choose M here: the module + will be called pwm-loongson1. +config
PWM_LP3943
tristate "TI/National Semiconductor LP3943 PWM support" depends onMFD_LP3943drivers/pwm/Makefile +0
-0@@ -19,6 +19,7 @@ obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.oobj-$(CONFIG_PWM_IMG) += pwm-img.o
obj-$(CONFIG_PWM_IMX) += pwm-imx.o
obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o
+obj-$(CONFIG_PWM_LOONGSON1) += pwm-loongson1.o
obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o
obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o
obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.odrivers/pwm/pwm-loongson1.c new file +45
-0@@ -0,0 +1,169 @@ +/* +* Copyright (c) 2017 Yang Ling <[email protected]> +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2 of the License, or (at your +* option) any later version. +*/ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <loongson1.h> + +struct ls1x_pwm_chip { +struct clk *clk; +void __iomem *base; +struct pwm_chip chip; +}; + +struct ls1x_pwm_channel { +u32 period_ns; +u32 duty_ns; +}; +static inline struct ls1x_pwm_chip *to_ls1x_pwm_chip (struct pwm_chip *chip) +{ + return container_of(chip, struct ls1x_pwm_chip, chip); +} + +static int ls1x_pwm_request (struct pwm_chip *chip,struct pwm_device *pwm) +{ +struct ls1x_pwm_channel *chan =NULL ; + + chan =devm_kzalloc (chip->dev,sizeof (*chan),GFP_KERNEL ); +if (!chan) +return -ENOMEM ; + +pwm_set_chip_data (pwm, chan); + +return 0 ; +} + +static void ls1x_pwm_free (struct pwm_chip *chip,struct pwm_device *pwm) +{
Quoted 8 lines
drivers/pwm/pwm-loongson1.c line 50
Quoted 4 lines
drivers/pwm/pwm-loongson1.c new file +4
-0+devm_kfree (chip->dev,pwm_get_chip_data (pwm)); +pwm_set_chip_data (pwm,NULL ); +} +Period and duty are stored in the
pwm_devicealready, so you're just duplicating the same data here. If you removels1x_pwm_channelthen all of the code above becomes unnecessary ...
Remove ls1x_pwm_channel.
Quoted 14 lines
drivers/pwm/pwm-loongson1.c line 61
Quoted 12 lines
drivers/pwm/pwm-loongson1.c new file +11
-0+static int ls1x_pwm_config (struct pwm_chip *chip,struct pwm_device *pwm, +int duty_ns,int period_ns) +{ +struct ls1x_pwm_chip *pc =to_ls1x_pwm_chip (chip); +struct ls1x_pwm_channel *chan =pwm_get_chip_data (pwm); +unsigned long long tmp; +unsigned long period, duty; + +if (period_ns == chan->period_ns && duty_ns == chan->duty_ns) +return 0 ; +above check is already handled in pwm core
Remove it.
Quoted 18 lines
Quoted 18 lines
drivers/pwm/pwm-loongson1.c new file +17
-0+ tmp = (unsigned long long )clk_get_rate (pc->clk) * period_ns; +do_div (tmp,1000000000 ); + period = tmp; + + tmp = (unsigned long long )period * duty_ns; +do_div (tmp, period_ns); + duty = period - tmp; + +if (duty >= period) + duty = period -1 ; + +if (duty >>24 || period >>24 ) +return -EINVAL ; + + chan->period_ns = period_ns; + chan->duty_ns = duty_ns; +
Quoted 8 lines
drivers/pwm/pwm-loongson1.c line 82
Quoted 4 lines
drivers/pwm/pwm-loongson1.c new file +4
-0+writel (duty, pc->base +PWM_HRC (pwm->hwpwm)); +writel (period, pc->base +PWM_LRC (pwm->hwpwm)); +writel (0x00 , pc->base +PWM_CNT (pwm->hwpwm)); +
PWM_HRCandPWM_LRCnames suggest that you're using high/low state counters here rather than duty/period - but with no documentation I'm just guessing here.
Indeed, the high/low state counters is used here.
Change the name to duty_cnt/period_cnt.
Quoted 53 lines
Quoted 40 lines
drivers/pwm/pwm-loongson1.c new file +39
-0+return 0 ; +} + +static int ls1x_pwm_enable (struct pwm_chip *chip,struct pwm_device *pwm) +{ +struct ls1x_pwm_chip *pc =to_ls1x_pwm_chip (chip); + +writel (CNT_RST , pc->base +PWM_CTRL (pwm->hwpwm)); +writel (CNT_EN , pc->base +PWM_CTRL (pwm->hwpwm)); + +return 0 ; +} + +static void ls1x_pwm_disable (struct pwm_chip *chip,struct pwm_device *pwm) +{ +struct ls1x_pwm_chip *pc =to_ls1x_pwm_chip (chip); + +writel (PWM_OE , pc->base +PWM_CTRL (pwm->hwpwm)); +} + +static const struct pwm_ops ls1x_pwm_ops = { + .request = ls1x_pwm_request, + .free = ls1x_pwm_free, + .config = ls1x_pwm_config, + .enable = ls1x_pwm_enable, + .disable = ls1x_pwm_disable, + .owner =THIS_MODULE , +}; + +static int ls1x_pwm_probe (struct platform_device *pdev) +{ +struct ls1x_pwm_chip *pc =NULL ; +struct resource *res =NULL ; + + pc =devm_kzalloc (&pdev->dev,sizeof (*pc),GFP_KERNEL ); +if (!pc) +return -ENOMEM ; + + res =platform_get_resource (pdev,IORESOURCE_MEM ,0 );drivers/pwm/pwm-loongson1.c line 131
Quoted 10 lines
drivers/pwm/pwm-loongson1.c new file +10
-0+ pc->base =devm_ioremap_resource (&pdev->dev, res); +if (IS_ERR (pc->base)) +return PTR_ERR (pc->base); + + pc->clk =devm_clk_get (&pdev->dev,"ls1x-pwmtimer" ); +if (IS_ERR (pc->clk)) { +dev_err (&pdev->dev,"failed to get %s clock\n" , pdev->name); +return PTR_ERR (pc->clk); + } +clk_prepare_enable (pc->clk);Should check for errors here
Quoted 10 lines
drivers/pwm/pwm-loongson1.c line 140
Quoted 8 lines
drivers/pwm/pwm-loongson1.c new file +8
-0+ pc->chip.ops = &ls1x_pwm_ops; + pc->chip.dev = &pdev->dev; + pc->chip.base =-1 ; + pc->chip.npwm =4 ; + +platform_set_drvdata (pdev, pc); + +return pwmchip_add (&pc->chip);Should check for errors (and disable clk appropriately)
These will be modified.
Quoted 27 lines
Quoted 27 lines
drivers/pwm/pwm-loongson1.c new file +26
-0+} + +static int ls1x_pwm_remove (struct platform_device *pdev) +{ +struct ls1x_pwm_chip *pc =platform_get_drvdata (pdev); +int ret; + + ret =pwmchip_remove (&pc->chip); +if (ret <0 ) +return ret; + +clk_disable_unprepare (pc->clk); + +return 0 ; +} + +static struct platform_driver ls1x_pwm_driver = { + .driver = { + .name ="ls1x-pwm" , + }, + .probe = ls1x_pwm_probe, + .remove = ls1x_pwm_remove, +}; +module_platform_driver (ls1x_pwm_driver); + +MODULE_AUTHOR ("Yang Ling <[email protected]>" );
Quoted 8 lines
drivers/pwm/pwm-loongson1.c line 169
Quoted 4 lines
drivers/pwm/pwm-loongson1.c new file +3
-0+MODULE_DESCRIPTION ("Loongson1 PWM driver" ); +MODULE_ALIAS ("platform:loongson1-pwm" ); +MODULE_LICENSE ("GPL" );Best regards,
Marcin
Thanks for your friendly reminder.
Yang
Hi Yang,
Quoted 2 lines · Yang Ling
Quoted 2 lines
drivers/pwm/pwm-loongson1.c line 63
Quoted 2 lines
drivers/pwm/pwm-loongson1.c new file +2
-0+ tmp = (unsigned long long )clk_get_rate (pc->clk) * period_ns; +do_div (tmp,1000000000 );
NSEC_PER_SEC ?
Quoted 24 lines
Quoted 15 lines
Quoted 15 lines
drivers/pwm/pwm-loongson1.c new file +15
-0+ period = tmp; + + tmp = (unsigned long long )period * duty_ns; +do_div (tmp, period_ns); + duty = period - tmp; + +if (duty >= period) + duty = period -1 ; + +if (duty >>24 || period >>24 ) +return -EINVAL ; + + chan->period_ns = period_ns; + chan->duty_ns = duty_ns; +Quoted 8 lines
drivers/pwm/pwm-loongson1.c line 82
Quoted 4 lines
drivers/pwm/pwm-loongson1.c new file +4
-0+writel (duty, pc->base +PWM_HRC (pwm->hwpwm)); +writel (period, pc->base +PWM_LRC (pwm->hwpwm)); +writel (0x00 , pc->base +PWM_CNT (pwm->hwpwm)); +
PWM_HRCandPWM_LRCnames suggest that you're using high/low state counters here rather than duty/period - but with no documentation I'm just guessing here.
Quoted 4 lines
Indeed, the high/low state counters is used here.
Change the name to duty_cnt/period_cnt.
What I was referring to here is that if you have a high/low value counters that you enter then these are not the same as duty/period, in simple terms:high_cnt = duty_cntlow_cnt = period_cnt - duty_cnt
so please double check that this is what you want to be doing? As the names used suggest that this code may be wrong. Or maybe what you're doing is correct but the register access macros have misleading names?
Marcin
Hi, Marcin,
I am sorry for the late reply.
Quoted 9 lines · Marcin Nowakowski
Hi Yang,
Quoted 2 lines · Yang Ling
Quoted 2 lines
drivers/pwm/pwm-loongson1.c line 63
Quoted 2 lines
drivers/pwm/pwm-loongson1.c new file +2
-0+ tmp = (unsigned long long )clk_get_rate (pc->clk) * period_ns; +do_div (tmp,1000000000 );
NSEC_PER_SEC?
Indeed, NSEC_PER_SEC should be used.
Quoted 29 lines
Quoted 24 lines
Quoted 15 lines
Quoted 15 lines
drivers/pwm/pwm-loongson1.c new file +15
-0+ period = tmp; + + tmp = (unsigned long long )period * duty_ns; +do_div (tmp, period_ns); + duty = period - tmp; + +if (duty >= period) + duty = period -1 ; + +if (duty >>24 || period >>24 ) +return -EINVAL ; + + chan->period_ns = period_ns; + chan->duty_ns = duty_ns; +Quoted 8 lines
drivers/pwm/pwm-loongson1.c line 82
Quoted 4 lines
drivers/pwm/pwm-loongson1.c new file +4
-0+writel (duty, pc->base +PWM_HRC (pwm->hwpwm)); +writel (period, pc->base +PWM_LRC (pwm->hwpwm)); +writel (0x00 , pc->base +PWM_CNT (pwm->hwpwm)); +
PWM_HRCandPWM_LRCnames suggest that you're using high/low state counters here rather than duty/period - but with no documentation I'm just guessing here.Quoted 4 lines
Indeed, the high/low state counters is used here.
Change the name to duty_cnt/period_cnt.
Quoted 9 lines
What I was referring to here is that if you have a high/low value counters that you enter then these are not the same as duty/period, in simple terms:
high_cnt=duty_cntlow_cnt=period_cnt-duty_cntso please double check that this is what you want to be doing? As the names used suggest that this code may be wrong. Or maybe what you're doing is correct but the register access macros have misleading names?
The macro definition of the register here is misleading. I will fix these problems afterwards.
Thanks for your friendly reminder.
Yang