Re: [PATCH v2] pwm: add pwm driver for HiSilicon BVT SOCs

From: Jian Yuan
Date: Thu Aug 25 2016 - 05:17:23 EST




On 2016/8/24 21:05, Thierry Reding wrote:
> On Mon, Aug 22, 2016 at 03:50:13PM +0800, Jian Yuan wrote:
>> From: yuanjian <yuanjian12@xxxxxxxxxxxxx>
>>
>> Add pwm driver for HiSilicon BVT SOCs
>
> pwm -> PWM, please. It'd be good to have more information here about
> what the hardware can do, where to find it, etc.
>
Not sure what you mean? Should I describe what the PWM or the BVT SoCs can do?
>>
>> Reviewed-by: Jiancheng Xue <xuejiancheng@xxxxxxxxxxxxx>
>> Signed-off-by: Jian Yuan <yuanjian12@xxxxxxxxxxxxx>
>> ---
>> Change Log:
>> v2:
>> The number of PWMs is change to be probeable based on the compatible string.
>>
>> .../devicetree/bindings/pwm/pwm-hibvt.txt | 18 ++
>> drivers/pwm/Kconfig | 10 +
>> drivers/pwm/Makefile | 1 +
>> drivers/pwm/pwm-hibvt.c | 274 +++++++++++++++++++++
>> 4 files changed, 303 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/pwm/pwm-hibvt.txt
>> create mode 100644 drivers/pwm/pwm-hibvt.c
>>
>> diff --git a/Documentation/devicetree/bindings/pwm/pwm-hibvt.txt b/Documentation/devicetree/bindings/pwm/pwm-hibvt.txt
>> new file mode 100644
>> index 0000000..1274119
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/pwm/pwm-hibvt.txt
>> @@ -0,0 +1,18 @@
>> +Hisilicon PWM controller
>> +
>> +Required properties:
>> +-compatible: should contain one soc specific compatible string and one generic compatible
>> +string "hisilicon, hibvt-pwm". The soc specific strings supported including:
>
> Why the generic compatible string? You've already shown in the driver
> that the two versions you support aren't compatible.
>
The generic compatible string should be contained in every devicetree bindings that BVT Socs support.
But I'll add another specific compatible string to distinguish it from "hisilicon,hi3516cv300-pwm".

> Also soc -> SoC, please.
>
>> + "hisilicon,hi3516cv300-pwm"
>> +- reg: physical base address and length of the controller's registers.
>> +- clocks: phandle and clock specifier of the PWM reference clock.
>> +- resets: offset address and offset bit for reset or unreset of the controller.
>
> That's weird. Usually this should say just that the property should
> contain the phandle plus a reset specifier for the controller reset. The
> above description already defines what the specifier looks like, which
> may not be true on all SoCs that this hardware gets integrated into.
>
Ok.
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index c182efc..3c48768 100644
>> --- a/drivers/pwm/Kconfig
>> +++ b/drivers/pwm/Kconfig
>> @@ -158,6 +158,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_HIBVT
>> + tristate "HiSilicon BVT PWM support"
>> + depends on ARCH_HISI
>> + help
>> + Generic PWM framework driver for hisilicon BVT SOCs.
>
> Please use a consistent spelling for HiSilicon. Also: SOC -> SoC.
>
Ok.
>> @@ -438,4 +447,5 @@ config PWM_VT8500
>> To compile this driver as a module, choose M here: the module
>> will be called pwm-vt8500.
>>
>> +
>
> Please don't add this extra blank line.
>
Ok.
>> diff --git a/drivers/pwm/pwm-hibvt.c b/drivers/pwm/pwm-hibvt.c
> [...]
>> new file mode 100644
>> index 0000000..84f617e
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-hibvt.c
>> @@ -0,0 +1,274 @@
>> +/*
>> + * PWM Controller Driver for HiSilicon BVT SOCs
>> + *
>> + * Copyright (c) 2016 HiSilicon Technologies Co., Ltd.
>> + *
>> + * 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.
>> + *
>> + * This program is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
>> + * GNU General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program. If not, see <http://www.gnu.org/licenses/>.
>> + */
>> +
>> +#include <linux/bitops.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pwm.h>
>> +#include <linux/reset.h>
>> +
>> +#define PWM_CFG0_ADDR(x) (((x) * 0x20) + 0x0)
>> +#define PWM_CFG1_ADDR(x) (((x) * 0x20) + 0x4)
>> +#define PWM_CFG2_ADDR(x) (((x) * 0x20) + 0x8)
>> +#define PWM_CTRL_ADDR(x) (((x) * 0x20) + 0xC)
>> +
>> +#define PWM_ENABLE_SHIFT 0
>> +#define PWM_ENABLE_MASK BIT(0)
>> +
>> +#define PWM_POLARITY_SHIFT 1
>> +#define PWM_POLARITY_MASK BIT(1)
>> +
>> +#define PWM_KEEP_SHIFT 2
>> +#define PWM_KEEP_MASK BIT(2)
>> +
>> +#define PWM_PERIOD_MASK GENMASK(31, 0)
>> +#define PWM_DUTY_MASK GENMASK(31, 0)
>
> There's inconsistent padding in the above. I thought checkpatch warned
> about this nowadays.
>
>> +
>> +struct hibvt_pwm_chip {
>> + struct pwm_chip chip;
>> + struct clk *clk;
>> + void __iomem *mmio_base;
>> + struct reset_control *rstc;
>> +};
>
> No artificial padding with tabs above. A single space between type and
> variable name is good enough. Also why the extra mmio_ prefix for the
> I/O memory base address? You use the much shorter "base" for variables
> elsewhere, why not in this structure?
>
Ok.
>> +
>> +static int pwm_num_array[] = {8, 4};
>
> static const unsigned, please. Or better yet, introduce a separate
> structure for this type of SoC-specific data. Something like:
>
> struct hibvt_pwm_soc {
> unsigned int num_pwms;
> };
>
Good idea.
>> +
>> +static inline
>> +struct hibvt_pwm_chip *to_hibvt_pwm_chip(struct pwm_chip *chip)
>
> It's more idiomatic to put the return type on the first line as well.
>
Ok.
>> +{
>> + return container_of(chip, struct hibvt_pwm_chip, chip);
>> +}
>> +
>> +static void hibvt_pwm_set_bits(void __iomem *base, unsigned int offset,
>> + unsigned int mask, unsigned int data)
>
> Please align arguments on subsequent lines with the first argument on
> the first line. And use u32 for mask and data since that's what readl()
> and writel() use.
>
Ok.
>> +{
>> + void __iomem *address = base + offset;
>> + unsigned int value;
>
> u32, please.
>
Ok.
>> +
>> + value = readl(address);
>> + value &= ~mask;
>> + value |= (data & mask);
>> + writel(value, address);
>> +}
>> +
>> +static int hibvt_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
>> +{
>> + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip);
>> + unsigned int offset;
>> +
>> + offset = PWM_CTRL_ADDR(pwm->hwpwm);
>> + hibvt_pwm_set_bits(hi_pwm_chip->mmio_base, offset,
>
> I don't think the temporary variable "offset" is useful here.
>
>> + PWM_ENABLE_MASK, 0x1);
>
> Also, please align this properly.
>
Ok.
>> +
>> + return 0;
>> +}
>> +
>> +static void hibvt_pwm_disable(struct pwm_chip *chip,
>> + struct pwm_device *pwm)
>
> The above will fit on a single line.
>
Ok.
>> +{
>> + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip);
>> + unsigned int offset;
>> +
>> + offset = PWM_CTRL_ADDR(pwm->hwpwm);
>> + hibvt_pwm_set_bits(hi_pwm_chip->mmio_base, offset,
>> + PWM_ENABLE_MASK, 0x0);
>
> Again, I don't think the offset variable is any good here.
>
Ok.
>> +}
>> +
>> +static int hibvt_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
>> + int duty_cycle_ns, int period_ns)
>> +{
>> + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip);
>> + unsigned int offset;
>> + unsigned int freq;
>> + unsigned int period_num, duty_num;
>
> Why the _num suffix? You could just name them "period" and "duty". Also
> you can put the last four variables above on the same line.
>
Ok.
>> +
>> + freq = div_u64(clk_get_rate(hi_pwm_chip->clk), 1000000);
>> +
>> + period_num = div_u64(freq * period_ns, 1000);
>> + duty_num = div_u64(period_num * duty_cycle_ns, period_ns);
>> +
>> + offset = PWM_CFG0_ADDR(pwm->hwpwm);
>> + hibvt_pwm_set_bits(hi_pwm_chip->mmio_base, offset,
>> + PWM_PERIOD_MASK, period_num);
>> +
>> + offset = PWM_CFG1_ADDR(pwm->hwpwm);
>> + hibvt_pwm_set_bits(hi_pwm_chip->mmio_base, offset,
>> + PWM_DUTY_MASK, duty_num);
>
> offset can be dropped here as well, in my opinion.
>
Ok.
>> +
>> + return 0;
>> +}
>> +
>> +static int hibvt_pwm_set_polarity(struct pwm_chip *chip,
>> + struct pwm_device *pwm,
>> + enum pwm_polarity polarity)
>> +{
>> + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip);
>> + unsigned int offset;
>> +
>> + offset = PWM_CTRL_ADDR(pwm->hwpwm);
>> + if (polarity == PWM_POLARITY_INVERSED)
>> + hibvt_pwm_set_bits(hi_pwm_chip->mmio_base, offset,
>> + PWM_POLARITY_MASK, (0x1 << PWM_POLARITY_SHIFT));
>> + else
>> + hibvt_pwm_set_bits(hi_pwm_chip->mmio_base, offset,
>> + PWM_POLARITY_MASK, (0x0 << PWM_POLARITY_SHIFT));
>> +
>> + return 0;
>> +}
>> +
>> +void hibvt_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>> + struct pwm_state *state)
>> +{
>> + struct hibvt_pwm_chip *hi_pwm_chip = to_hibvt_pwm_chip(chip);
>> + void __iomem *base;
>> + unsigned int offset;
>> + unsigned int freq;
>> + unsigned int period_num, duty_num;
>> + unsigned int enable;
>
> I think you could trim the list of variables down a little. offset can
> go away for reasons given above. And of the other four, the only one
> that you need to reuse is freq, so period_num, duty_num and enable can
> all be replaced by a "value" variable, for example. Also, make that an
> u32, because that's what readl() returns.
>
Fine, it looks like a better solution.
>> +
>> + freq = div_u64(clk_get_rate(hi_pwm_chip->clk), 1000000);
>> + base = hi_pwm_chip->mmio_base;
>> +
>> + offset = PWM_CFG0_ADDR(pwm->hwpwm);
>> + period_num = readl(base + offset);
>> + state->period = div_u64(period_num * 1000, freq);
>> +
>> + offset = PWM_CFG1_ADDR(pwm->hwpwm);
>> + duty_num = readl(base + offset);
>> + state->duty_cycle = div_u64(duty_num * 1000, freq);
>> +
>> + offset = PWM_CTRL_ADDR(pwm->hwpwm);
>> + enable = readl(base + offset);
>> + state->enabled = (PWM_ENABLE_MASK & enable);
>> +}
>> +
>> +static struct pwm_ops hibvt_pwm_ops = {
>> + .enable = hibvt_pwm_enable,
>> + .disable = hibvt_pwm_disable,
>> + .config = hibvt_pwm_config,
>> + .set_polarity = hibvt_pwm_set_polarity,
>> + .get_state = hibvt_pwm_get_state,
>
> Please don't mix legacy and atomic support. If you support .get_state(),
> you should implement .apply() instead of .enable()/.disable()/.config().
> Also, please remove the artificial padding.
>
I agree to implement .apply() function.
>> +
>> + .owner = THIS_MODULE,
>> +};
>> +
>> +static const struct of_device_id hibvt_pwm_of_match[];
>
> No need for this...
>
Ok.
>> +
>> +static int hibvt_pwm_probe(struct platform_device *pdev)
>> +{
>> + struct hibvt_pwm_chip *pwm_chip;
>> + const struct of_device_id *of_id =
>> + of_match_device(hibvt_pwm_of_match, &pdev->dev);
>
> ... if you use of_device_get_match_data() here.
>
>> + struct resource *res;
>> + int ret = 0;
>
> No need to initialize to 0 here.
>
>> + int i;
>
> unsigned int, please.
>
>> + int offset;
>> + int pwm_nums;
>
> I think both of these can be dropped. See below.
>
>> +
>> + if (!of_id)
>> + return -ENODEV;
>> +
>> + pwm_chip = devm_kzalloc(&pdev->dev, sizeof(*pwm_chip), GFP_KERNEL);
>> + if (pwm_chip == NULL)
>> + return -ENOMEM;
>> +
>> + pwm_chip->clk = devm_clk_get(&pdev->dev, NULL);
>> + if (IS_ERR(pwm_chip->clk)) {
>> + dev_err(&pdev->dev, "getting clock failed with %ld\n",
>> + PTR_ERR(pwm_chip->clk));
>> + return PTR_ERR(pwm_chip->clk);
>> + }
>> +
>> + pwm_nums = *((int *)of_id->data);
>
> This becomes a lot easier to read if you use a proper structure for the
> SoC specific data:
>
> const struct hibvt_pwm_soc *soc = of_device_get_match_data(&pdev->dev);
>
> pwm_chip->chip.npwm = soc->num_pwms;
>
That's right.
>> +
>> + pwm_chip->chip.ops = &hibvt_pwm_ops;
>> + pwm_chip->chip.dev = &pdev->dev;
>> + pwm_chip->chip.base = -1;
>> + pwm_chip->chip.npwm = pwm_nums;
>> +
>> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + pwm_chip->mmio_base = devm_ioremap_resource(&pdev->dev, res);
>> + if (IS_ERR(pwm_chip->mmio_base))
>> + return PTR_ERR(pwm_chip->mmio_base);
>> +
>> + ret = clk_prepare_enable(pwm_chip->clk);
>> + if (ret < 0)
>> + return ret;
>> +
>> + pwm_chip->rstc = devm_reset_control_get(&pdev->dev, NULL);
>> + if (IS_ERR(pwm_chip->rstc))
>> + return PTR_ERR(pwm_chip->rstc);
>> +
>
> You might want to move clk_prepare_enable() below this so you don't keep
> it enabled when the reset control can't be requested.
>
Ok, I'll add it.
>> + reset_control_assert(pwm_chip->rstc);
>> + msleep(30);
>> + reset_control_deassert(pwm_chip->rstc);
>> +
>> + ret = pwmchip_add(&pwm_chip->chip);
>> + if (ret < 0)
>> + return ret;
>
> You might want to assert the reset and disable and unprepare the clock
> when this fails.
>
>> +
>> + for (i = 0; i < pwm_nums; i++) {
>> + offset = PWM_CTRL_ADDR(i);
>> + hibvt_pwm_set_bits(pwm_chip->mmio_base, offset,
>
> Things will still fit on one line if you use PWM_CTRL_ADDR(i) in place
> above, so I don't think you gain anything by adding the temporary
> variable.
>
>> + PWM_KEEP_MASK, (0x1 << PWM_KEEP_SHIFT));
>> + }
>> +
>> + platform_set_drvdata(pdev, pwm_chip);
>> +
>> + return 0;
>> +}
>> +
>> +static int hibvt_pwm_remove(struct platform_device *pdev)
>> +{
>> + struct hibvt_pwm_chip *pwm_chip;
>> +
>> + pwm_chip = platform_get_drvdata(pdev);
>> + if (pwm_chip == NULL)
>> + return -ENODEV;
>
> There's no way this will ever happen.
>
>> +
>> + clk_disable_unprepare(pwm_chip->clk);
>
> What about the reset?
>
Ok.
>> +
>> + return pwmchip_remove(&pwm_chip->chip);
>> +}
>> +
>> +static const struct of_device_id hibvt_pwm_of_match[] = {
>> + {.compatible = "hisilicon,hibvt-pwm", .data = &pwm_num_array[0]},
>> + {.compatible = "hisilicon,hi3516cv300-pwm", .data = &pwm_num_array[1]},
>
> Spaces after { and before }, please.
>
Ok.
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(of, hibvt_pwm_of_match);
>> +
>> +static struct platform_driver hibvt_pwm_driver = {
>> + .driver = {
>> + .name = "hibvt-pwm",
>> + .of_match_table = hibvt_pwm_of_match,
>> + },
>> + .probe = hibvt_pwm_probe,
>> + .remove = hibvt_pwm_remove,
>> +};
>
> No need for the artificial padding, it's not working correctly for the
> .of_match_table field anyway.
>
>> +
>> +module_platform_driver(hibvt_pwm_driver);
>
> No blank line between the "};" and "module_platform_driver(...);" line,
> please.
>
>> +
>> +MODULE_AUTHOR("yuanjian12@xxxxxxxxxxxxx");
>
> It's customary to put your real name here.
>
>> +MODULE_DESCRIPTION("Hisilicon BVT SOCs PWM driver");
>
> Again, please use consistent spelling for HiSilicon.
>
>> +MODULE_LICENSE("GPL v2");
>
> The header comment says "GPL v2 or later", but "GPL v2" here means GPL
> v2 only.
>
Ok.
> Thierry
>

Thanks.
Jian Yuan