Re: [rfc]pwm: add xilinx pwm driver

From: Michal Simek
Date: Thu May 15 2014 - 05:49:29 EST


On 05/14/2014 01:26 PM, Bart Tanghe wrote:
> add Xilinx PWM support - axi timer hardware block
>
> Signed-off-by: Bart Tanghe <bart.tanghe@xxxxxxxxxxxxx>
> diff --git a/Documentation/devicetree/bindings/pwm/pwm-xlnx.txt b/Documentation/devicetree/bindings/pwm/pwm-xlnx.txt
> new file mode 100644
> index 0000000..cb48926
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/pwm-xlnx.txt
> @@ -0,0 +1,20 @@
> +Xilinx PWM controller
> +
> +Required properties:
> +- compatible: should be "xlnx,pwm-xlnx"
> +- add a clock source to the description
> +
> +Examples:
> +
> + axi_timer_0: timer@42800000 {
> + clock-frequency = <100000000>;

just remove this from example it is not needed and unused.


> + clocks = <&clkc 15>;
> + compatible = "xlnx,xlnx-pwm";
> + reg = <0x42800000 0x10000>;
> + xlnx,count-width = <0x20>;
> + xlnx,gen0-assert = <0x1>;
> + xlnx,gen1-assert = <0x1>;
> + xlnx,one-timer-only = <0x0>;
> + xlnx,trig0-assert = <0x1>;
> + xlnx,trig1-assert = <0x1>;
> + } ;

ok. This has to be done completely differently.
I have looked around and found one a little bit older
example but it is in the Linux kernel.
It is pwm-atmel-tcb.c driver and atmel_tclib.c and tcb_clksrc.c.

Arnd: Isn't there any newer better example how to manage it?

The latest timer driver is available here
arch/microblaze/kernel/timer.c which has support for big
and little endian.
There is probably no timer driver for old xilinx ppc405/ppc440
platforms but this timer can be also used there.

The timer driver needs to be moved from microblaze
folder to drivers/clocksource/ and should be just git mv
because I have done some changes some time ago
that it is compatible with clocksource drivers.

Back to atmel example - they are maintaining
internal list of timers (atmel_tclib.c)
and then clocksource driver (tcb_clksrc.c) is asking with atmel_tc_alloc() for it.
The same is for pwm driver (pwm-atmel-tcb.c).

They probably have all timers the same which is not
our case because they can be different but this can be solved
with flags.

From DT point of view I think this is the reasonable description

axi_timer_0: timer@42800000 {
clocks = <&clkc 15>;
compatible = "xlnx,xps-timer-1.00.a";
interrupt-parent = <&xps_intc_0>;
interrupts = < 3 2 >;
reg = <0x42800000 0x10000>;
xlnx,count-width = <0x20>;
xlnx,gen0-assert = <0x1>;
xlnx,gen1-assert = <0x1>;
xlnx,one-timer-only = <0x0>;
xlnx,trig0-assert = <0x1>;
xlnx,trig1-assert = <0x1>;
} ;


pwm {
compatible = "xlnx,timer-pwm";
#pwm-cells = <2>;
timer = <&axi_timer_0>;
};


Allocation function will also require to do a change
in clocksource driver because currently the first
timer in the DTS/system is taken for clocksource/clockevents
(others are just ignored).
That's why will be necessary to add clock-handle property
to cpu node to exactly describe which timer is system timer.
The same as is for interrupt-handle (more intc's can be in design).

cpus {
#address-cells = <1>;
#cpus = <1>;
#size-cells = <0>;
microblaze_0: cpu@0 {
compatible = "xlnx,microblaze-8.50.a";
interrupt-handle = <&microblaze_0_intc>;
...
clock-handle = <&axi_timer_X>;
...
} ;
} ;

Then clocksource driver will ask just exact timer which is suitable
to provide clocksource/clockevent. It can be also split and
use 2 cells format to specify which timer should be used.
clocksource-handle = <&axi_timer_X 0>;
clockeven-handle = <&axi_timer_X 1>;



> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index eece329..1d59b02 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -233,4 +233,17 @@ config PWM_VT8500
> To compile this driver as a module, choose M here: the module
> will be called pwm-vt8500.
>
> +config PWM_XLNX
> + tristate "Xilinx PWM support"
> + help
> + Generic PWM framework driver for xilinx axi timer.
> +
> + This driver implements the pwm channel of a xilinx axi timer hardware

in the next email you are writing about ppc. It means xilinx timer just
here. Because it can be opb/plb/axi now.

> + block.
> + The hardware block has to be configured with generate output signal
> + of timer 1 and timer 2 active high.
> +
> + To compile this driver as a module, choose M here: the module
> + will be called pwm-xlnx.
> +
> endif
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 8b754e4..e0c5f59 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -21,3 +21,4 @@ obj-$(CONFIG_PWM_TIPWMSS) += pwm-tipwmss.o
> obj-$(CONFIG_PWM_TWL) += pwm-twl.o
> obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o
> obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o
> +obj-$(CONFIG_PWM_XLNX) += pwm-xlnx.o
> diff --git a/drivers/pwm/pwm-xlnx.c b/drivers/pwm/pwm-xlnx.c
> new file mode 100644
> index 0000000..9c1be71
> --- /dev/null
> +++ b/drivers/pwm/pwm-xlnx.c
> @@ -0,0 +1,197 @@
> +/*
> + * pwm-xlnx driver
> + * Tested on zedboard - axi timer v2.00a
> + *
> + * Copyright (C) 2014 Thomas more

more?

> + *
> + * 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; version 2.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +
> +/*mmio regiser mapping*/

typo and use proper comment format, fix comment format globally.

> +
> +#define OFFSET 0x10
> +#define DUTY 0x14
> +#define PERIOD 0x04

this has to reuse proper register name from datasheet.

> +
> +/* configure the counters as 32 bit counters */
> +
> +#define PWM_CONF 0x00000206
> +#define PWM_ENABLE 0x00000080

same for bits.

> +
> +#define DRIVER_AUTHOR "Bart Tanghe <bart.tanghe@xxxxxxxxxxxxx>"
> +#define DRIVER_DESC "A Xilinx pwm driver"

Just use it directly below.

> +
> +struct xlnx_pwm_chip {
> + struct pwm_chip chip;
> + struct device *dev;
> + int scaler;
> + void __iomem *mmio_base;
> +};
> +
> +static inline struct xlnx_pwm_chip *to_xlnx_pwm_chip(
> + struct pwm_chip *chip){
> +
> + return container_of(chip, struct xlnx_pwm_chip, chip);
> +}
> +
> +static int xlnx_pwm_config(struct pwm_chip *chip,
> + struct pwm_device *pwm,
> + int duty_ns, int period_ns){
> +
> + struct xlnx_pwm_chip *pc;
> +
> + pc = container_of(chip, struct xlnx_pwm_chip, chip);
> +
> + iowrite32(duty_ns/pc->scaler - 2, pc->mmio_base + DUTY);
> + iowrite32(period_ns/pc->scaler - 2, pc->mmio_base + PERIOD);

not a proper coding style.


> +
> + return 0;
> +}
> +
> +static int xlnx_pwm_enable(struct pwm_chip *chip,
> + struct pwm_device *pwm){
> +
> + struct xlnx_pwm_chip *pc;
> +
> + pc = container_of(chip, struct xlnx_pwm_chip, chip);
> +
> + iowrite32(ioread32(pc->mmio_base) | PWM_ENABLE, pc->mmio_base);
> + iowrite32(ioread32(pc->mmio_base + OFFSET) | PWM_ENABLE,
> + pc->mmio_base + OFFSET);
> + return 0;
> +}
> +
> +static void xlnx_pwm_disable(struct pwm_chip *chip,
> + struct pwm_device *pwm)
> +{
> + struct xlnx_pwm_chip *pc;
> +
> + pc = to_xlnx_pwm_chip(chip);
> +
> + iowrite32(ioread32(pc->mmio_base) & ~PWM_ENABLE, pc->mmio_base);
> + iowrite32(ioread32(pc->mmio_base + OFFSET) & ~PWM_ENABLE,
> + pc->mmio_base + OFFSET);
> +}
> +
> +static int xlnx_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
> + enum pwm_polarity polarity)
> +{
> + struct xlnx_pwm_chip *pc;
> +
> + pc = to_xlnx_pwm_chip(chip);
> +
> + /* no implementation of polarity function
> + the axi timer hw block doesn't support this
> + */

wrong comment.

> +
> + return 0;
> +}
> +
> +static const struct pwm_ops xlnx_pwm_ops = {
> + .config = xlnx_pwm_config,
> + .enable = xlnx_pwm_enable,
> + .disable = xlnx_pwm_disable,
> + .set_polarity = xlnx_set_polarity,
> + .owner = THIS_MODULE,
> +};
> +
> +static int xlnx_pwm_probe(struct platform_device *pdev)
> +{
> + struct xlnx_pwm_chip *pwm;
> +
> + int ret;
> + struct resource *r;
> + u32 start, end;
> + struct clk *clk;
> +
> + pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
> + if (!pwm) {
> + dev_err(&pdev->dev, "failed to allocate memory\n");

don't need this error message. It is reported by core automatically.

> + return -ENOMEM;
> + }
> +
> + pwm->dev = &pdev->dev;
> +
> + clk = devm_clk_get(&pdev->dev, NULL);
> + if (IS_ERR(clk)) {
> + dev_err(&pdev->dev, "could not find clk: %ld\n", PTR_ERR(clk));
> + devm_kfree(&pdev->dev, pwm);

this is called automatically that's why you are calling devm_ functions.

> + return PTR_ERR(clk);
> + }
> +
> + pwm->scaler = (int)1000000000/clk_get_rate(clk);

magic value - comment will be useful.

> +
> + r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + pwm->mmio_base = devm_ioremap_resource(&pdev->dev, r);
> + if (IS_ERR(pwm->mmio_base)) {
> + devm_kfree(&pdev->dev, pwm);

ditto.

> + return PTR_ERR(pwm->mmio_base);
> + }
> +
> + start = r->start;
> + end = r->end;
> +
> + pwm->chip.dev = &pdev->dev;
> + pwm->chip.ops = &xlnx_pwm_ops;
> + pwm->chip.base = (int)&pdev->id;
> + pwm->chip.npwm = 1;
> +
> + ret = pwmchip_add(&pwm->chip);
> + if (ret < 0) {
> + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret);
> + devm_kfree(&pdev->dev, pwm);
> + return -1;
> + }
> +
> + /*set the pwm0 configuration*/
> + iowrite32(PWM_CONF, pwm->mmio_base);
> + iowrite32(PWM_CONF, pwm->mmio_base + OFFSET);
> +
> + platform_set_drvdata(pdev, pwm);
> +
> + return 0;
> +}
> +
> +static int xlnx_pwm_remove(struct platform_device *pdev)
> +{
> +
> + struct xlnx_pwm_chip *pc;
> + pc = platform_get_drvdata(pdev);

two spaces.

> +
> + if (WARN_ON(!pc))
> + return -ENODEV;
> +
> + return pwmchip_remove(&pc->chip);
> +}
> +
> +static const struct of_device_id xlnx_pwm_of_match[] = {
> + { .compatible = "xlnx,pwm-xlnx", },
> + { /* sentinel */ }
> +};
> +
> +MODULE_DEVICE_TABLE(of, xlnx_pwm_of_match);
> +
> +static struct platform_driver xlnx_pwm_driver = {
> + .driver = {
> + .name = "pwm-xlnx",
> + .owner = THIS_MODULE,
> + .of_match_table = xlnx_pwm_of_match,
> + },
> + .probe = xlnx_pwm_probe,
> + .remove = xlnx_pwm_remove,
> +};
> +module_platform_driver(xlnx_pwm_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR(DRIVER_AUTHOR);
> +MODULE_DESCRIPTION(DRIVER_DESC);


Thanks,
Michal


--
Michal Simek, Ing. (M.Eng), OpenPGP -> KeyID: FE3D1F91
w: www.monstr.eu p: +42-0-721842854
Maintainer of Linux kernel - Microblaze cpu - http://www.monstr.eu/fdt/
Maintainer of Linux kernel - Xilinx Zynq ARM architecture
Microblaze U-BOOT custodian and responsible for u-boot arm zynq platform


Attachment: signature.asc
Description: OpenPGP digital signature