[PATCH v3] pwm: add CSR SiRFSoC PWM driver

Barry Song 21cnbao at gmail.com
Fri Feb 28 05:33:19 EST 2014


2014-02-28 18:07 GMT+08:00 Barry Song <21cnbao at gmail.com>:
> 2014-02-28 17:06 GMT+08:00 Romain Izard <romain.izard.pro at gmail.com>:
>> Hello Barry,
>>
>> 2014-02-28 4:01 GMT+01:00 Barry Song <21cnbao at gmail.com>:
>>>
>>> Hi Romain,
>>>
>>> do you have a real user scenarios for this 32KHz? as the 1st step, i
>>> want a clean and general enough pwm driver, if there is any special
>>> requirement, i want to execute a "demanding page" when the "page
>>> fault" happened as this will make the whole flow more smooth. that
>>> means we make it lazy by incremental patches. but if you do want to
>>> use it for the moment, it is a "page fault" now. so we can have it
>>> immediately and it is better you can help to double-test :-)
>>
>> My use case is to connect a Bluetooth module, in fact with a CSR chip.
>> For testing, I will try to see what I can do, as for now my boards are not
>> running with the mainline. But I'm very interested in transitioning on the
>> mainline in due time, so that's the reason I keep an eye on the mailing
>> lists. Conversely, it's not critical at all to get the feature in right now, but
>> it should not be impossible to do so in the future, especially when taking
>> the 'stable interface' aspect of device tree bindings into account.
>>
>>> 2014-02-27 18:51 GMT+08:00 Romain Izard <romain.izard.pro at gmail.com>:
>>>> Perhaps this can be linked with the missing "clock-names" property, as this
>>>> will make it possible to add multiple functional clocks, with "iclk" matching
>>>> the interface clock (i.e. pwmc), and "fclk0", "fclk1", etc. matching
>>>> all supported
>>>> functional clocks.
>>>>
>>>> With only the 26 MHz clock, the node would look like:
>>>>
>>>> pwm: pwm at b0130000 {
>>>>         compatible = "sirf,prima2-pwm";
>>>>         #pwm-cells = <2>;
>>>>         reg = <0xb0130000 0x10000>;
>>>>         clocks = <&clks 21>,  <&clks 1>, <&clks 0>;
>>>>         clock-names = "iclk", "fclk0", "fclk3";
>>>> };
>>>>
>>
>> Aargh. I meant this:
>>
>> pwm: pwm at b0130000 {
>>         compatible = "sirf,prima2-pwm";
>>         #pwm-cells = <2>;
>>         reg = <0xb0130000 0x10000>;
>>         clocks = <&clks 21>,  <&clks 1>;
>>         clock-names = "iclk", "fclk0";
>> };
>>
>>>> The result would look like this with both the 26 MHz and the 32 kHz clocks:
>>>>
>>>> pwm: pwm at b0130000 {
>>>>         compatible = "sirf,prima2-pwm";
>>>>         #pwm-cells = <2>;
>>>>         reg = <0xb0130000 0x10000>;
>>>>         clocks = <&clks 21>,  <&clks 1>, <&clks 0>;
>>>>         clock-names = "iclk", "fclk0", "fclk3";
>>>> };
>>>>
>>>> And with all possible clocks:
>>>>
>>>> pwm: pwm at b0130000 {
>>>>         compatible = "sirf,prima2-pwm";
>>>>         #pwm-cells = <2>;
>>>>         reg = <0xb0130000 0x10000>;
>>>>         clocks = <&clks 21>,  <&clks 1>, <&clks 2>, <&clks 3>, <&clks
>>>> 0>, <&clks 4>;
>>>>         clock-names = "iclk", "fclk0", "fclk1", "fclk2", "fclk3", "fclk4";
>>>> };
>>>>
>>>> The code in the probe function would interpret the clock-name to build
>>>> the clock/index mapping table. This would not change a lot the binding you
>>>> proposed, as you still can describe only one functional clock, but it describes
>>>> which index in the configuration register is linked to the proposed clock.
>>>>
>>>
>>> yes. very clear. the only two things left
>>>
>>> 1.  fclk should be named for pwm not for rtc, osc and pll, so the
>>> names might be ugly by fclk0~N.
>>>
>> For me, it's important to keep the number in the name of the clock as it is
>> the source of the information for binding the device tree clock on one side
>> and the clock source index in the selection register on the other side. If
>> we do not do it this way, we cannot easily handle new clocks in the future
>> in the binding.
>>
>> On the other hand, I strongly dislike the current clock specification
>> as <&clks #x>.
>> It would be probably a good thing to add a header linking the numbers
>> to symbols,
>> and we would then get:
>>
>>         clocks = <&clks SIRFCLK_A6_PWM>,
>>                 <&clks SIRFCLK_A6_OSC>,
>>                 <&clks SIRFCLK_A6_PLL1>,
>>                 <&clks SIRFCLK_A6_PLL2>,
>>                 <&clks SIRFCLK_A6_RTC>,
>>                 <&clks SIRFCLK_16_PLL3>;
>>         clock-names = "iclk", "fclk0", "fclk1", "fclk2", "fclk3", "fclk4";
>>
>> Note that as the binary output of the device tree would not change with this, we
>> do not need to worry about backwards compatibility in this precise case.
>
> this pwm controller is prima2-compatible, so it is actually a pwm
> controller highly bound with the special SoC.
> really strange things are we actually care about the clock types. we
> actually can't think fclk0~fclk4 as same things. for pll, i think it
> is buggy to use. for rtc, it only service special target for providing
> bypass 32KHz clock.
>
> if we look this controller a separate IP, we need to look 5 clock
> source to be coequal. but it is not the real case.
> i think people will hate the things that we have a separate bypass
> mode for fclk3, why it is 3 but not 2, 1, 4 and 5?
>
> that is why i move to a MACRO 26MHz in the original codes as i think
> it is a prima2 controller but not a controller used by prima2 even
> though we always want a IP module to be a IP module suitable for all
> SoCs.
>

let's have a draft to look whether this is making anything better:

/*
 * SIRF serial SoC PWM device core driver
 *
 * Copyright (c) 2014 Cambridge Silicon Radio Limited, a CSR plc group company.
 *
 * Licensed under GPLv2.
 */
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/pwm.h>
#include <linux/of.h>
#include <linux/io.h>

#define SIRF_PWM_SELECT_PRECLK            0x0
#define SIRF_PWM_OE                0x4
#define SIRF_PWM_ENABLE_PRECLOCK        0x8
#define SIRF_PWM_ENABLE_POSTCLOCK        0xC
#define SIRF_PWM_GET_WAIT_OFFSET(n)        (0x10 + 0x8*n)
#define SIRF_PWM_GET_HOLD_OFFSET(n)        (0x14 + 0x8*n)

#define SIRF_PWM_TR_STEP(n)            (0x48 + 0x8*n)
#define SIRF_PWM_STEP_HOLD(n)            (0x4c + 0x8*n)

#define SRC_FIELD_SIZE                3
#define BYPASS_MODE_BIT                21
#define TRANS_MODE_SELECT_BIT            7

struct sirf_pwm {
    struct pwm_chip    chip;
    struct mutex mutex;
    void __iomem *base;
    struct clk *pwmc_clk;
    struct clk *sigsrc0_clk;
    struct clk *sigsrc3_clk;
    /*
     * PWM controller uses OSC(default 26MHz) or RTC(default 32768Hz) clock
     * to generate PWM signals instead of the clock of the controller
     */
    unsigned long sigsrc0_clk_rate;
    unsigned long sigsrc3_clk_rate;
};

static inline struct sirf_pwm *to_sirf_pwm_chip(struct pwm_chip *chip)
{
    return container_of(chip, struct sirf_pwm, chip);
}

static u32 sirf_pwm_ns_to_cycles(struct pwm_chip *chip, u32 time_ns)
{
    struct sirf_pwm *spwm = to_sirf_pwm_chip(chip);
    u64 dividend;
    u32 cycle;

    dividend = (u64)spwm->sigsrc0_clk_rate * time_ns + NSEC_PER_SEC / 2;
    do_div(dividend, NSEC_PER_SEC);

    cycle = dividend;

    return cycle > 1 ? cycle : 1;
}

static int sirf_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
            int duty_ns, int period_ns)
{
    u32 period_cycles, high_cycles, low_cycles;
    u32 val;
    struct sirf_pwm *spwm = to_sirf_pwm_chip(chip);
    if (unlikely(period_ns == NSEC_PER_SEC/spwm->sigsrc3_clk_rate)) {
        /*
         * sigsrc 3 is RTC with typical frequency 32KHz,
         * bypass RTC clock to WiFi/Bluetooth module
         */
        mutex_lock(&spwm->mutex);

        val = readl(spwm->base + SIRF_PWM_SELECT_PRECLK);
        val |= 0x1 << (BYPASS_MODE_BIT + pwm->hwpwm);
        val &= ~(0x7 << (SRC_FIELD_SIZE * pwm->hwpwm));
        val |= 3 << (SRC_FIELD_SIZE * pwm->hwpwm);
        writel(val, spwm->base + SIRF_PWM_SELECT_PRECLK);

        mutex_unlock(&spwm->mutex);
    } else {
        /* use OSC to generate PWM signals */
        period_cycles = sirf_pwm_ns_to_cycles(chip, period_ns);
        if (period_cycles == 1)
            return -EINVAL;

        high_cycles = sirf_pwm_ns_to_cycles(chip, duty_ns);
        low_cycles = period_cycles - high_cycles;

        mutex_lock(&spwm->mutex);

        val = readl(spwm->base + SIRF_PWM_SELECT_PRECLK);
        val &= ~(0x1 << (BYPASS_MODE_BIT + pwm->hwpwm));
        val &= ~(0x7 << (SRC_FIELD_SIZE * pwm->hwpwm));
        writel(val, spwm->base + SIRF_PWM_SELECT_PRECLK);

        if (high_cycles == period_cycles) {
            high_cycles--;
            low_cycles = 1;
        }

        writel(high_cycles - 1,
            spwm->base + SIRF_PWM_GET_WAIT_OFFSET(pwm->hwpwm));
        writel(low_cycles - 1,
            spwm->base + SIRF_PWM_GET_HOLD_OFFSET(pwm->hwpwm));

        mutex_unlock(&spwm->mutex);
    }

    return 0;
}

static int sirf_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
    struct sirf_pwm *spwm = to_sirf_pwm_chip(chip);
    u32 val;

    mutex_lock(&spwm->mutex);

    /* disable preclock */
    val = readl(spwm->base + SIRF_PWM_ENABLE_PRECLOCK);
    val &= ~(1 << pwm->hwpwm);
    writel(val, spwm->base + SIRF_PWM_ENABLE_PRECLOCK);

    /* select preclock source must after disable preclk*/
    val = readl(spwm->base + SIRF_PWM_SELECT_PRECLK);
    val &= ~(0x7 << (SRC_FIELD_SIZE * pwm->hwpwm));
    writel(val, spwm->base + SIRF_PWM_SELECT_PRECLK);
    /* wait for some time */
    usleep_range(100, 100);

    /* enable preclock */
    val = readl(spwm->base + SIRF_PWM_ENABLE_PRECLOCK);
    val |= (1 << pwm->hwpwm);
    writel(val, spwm->base + SIRF_PWM_ENABLE_PRECLOCK);

    /* enable post clock*/
    val = readl(spwm->base + SIRF_PWM_ENABLE_POSTCLOCK);
    val |= (1 << pwm->hwpwm);
    writel(val, spwm->base + SIRF_PWM_ENABLE_POSTCLOCK);

    /* enable output */
    val = readl(spwm->base + SIRF_PWM_OE);
    val |= 1 << pwm->hwpwm;
    val |= 1 << (pwm->hwpwm + TRANS_MODE_SELECT_BIT);

    writel(val, spwm->base + SIRF_PWM_OE);

    mutex_unlock(&spwm->mutex);

    return 0;
}

static void sirf_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
{
    u32 val;
    struct sirf_pwm *spwm = to_sirf_pwm_chip(chip);

    mutex_lock(&spwm->mutex);

    /* disable output */
    val = readl(spwm->base + SIRF_PWM_OE);
    val &= ~(1 << pwm->hwpwm);
    writel(val, spwm->base + SIRF_PWM_OE);

    /* disable postclock */
    val = readl(spwm->base + SIRF_PWM_ENABLE_POSTCLOCK);
    val &= ~(1 << pwm->hwpwm);
    writel(val, spwm->base + SIRF_PWM_ENABLE_POSTCLOCK);

    /* disable preclock */
    val = readl(spwm->base + SIRF_PWM_ENABLE_PRECLOCK);
    val &= ~(1 << pwm->hwpwm);
    writel(val, spwm->base + SIRF_PWM_ENABLE_PRECLOCK);

    mutex_unlock(&spwm->mutex);
}

static const struct pwm_ops sirf_pwm_ops = {
    .enable = sirf_pwm_enable,
    .disable = sirf_pwm_disable,
    .config = sirf_pwm_config,
    .owner = THIS_MODULE,
};

static int sirf_pwm_probe(struct platform_device *pdev)
{
    struct sirf_pwm *spwm;
    struct resource *mem_res;
    int ret;

    spwm = devm_kzalloc(&pdev->dev, sizeof(*spwm),
            GFP_KERNEL);
    if (!spwm)
        return -ENOMEM;

    platform_set_drvdata(pdev, spwm);

    mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    spwm->base = devm_ioremap_resource(&pdev->dev, mem_res);
    if (!spwm->base)
        return -ENOMEM;

    /*
     * clock for PWM controller
     */
    spwm->pwmc_clk = devm_clk_get(&pdev->dev, "pwmc");
    if (IS_ERR(spwm->pwmc_clk)) {
        dev_err(&pdev->dev, "failed to get PWM controller clock\n");
        return PTR_ERR(spwm->pwmc_clk);
    }

    ret = clk_prepare_enable(spwm->pwmc_clk);
    if (ret)
        return ret;

    /*
     * clocks to generate PWM signals
     */
    spwm->sigsrc0_clk = devm_clk_get(&pdev->dev, "sigsrc0");
    if (IS_ERR(spwm->sigsrc0_clk)) {
        dev_err(&pdev->dev, "failed to get PWM signal source clock0\n");
        ret = PTR_ERR(spwm->sigsrc0_clk);
        goto err_src0_clk;
    }

    ret = clk_prepare_enable(spwm->sigsrc0_clk);
    if (ret)
        goto err_src0_clk;

    spwm->sigsrc0_clk_rate = clk_get_rate(spwm->sigsrc0_clk);

    spwm->sigsrc3_clk = devm_clk_get(&pdev->dev, "sigsrc3");
    if (IS_ERR(spwm->sigsrc3_clk)) {
        dev_err(&pdev->dev, "failed to get PWM signal source clock3\n");
        ret = PTR_ERR(spwm->sigsrc3_clk);
        goto err_src3_clk;
    }

    ret = clk_prepare_enable(spwm->sigsrc3_clk);
    if (ret)
        goto err_src3_clk;

    spwm->sigsrc3_clk_rate = clk_get_rate(spwm->sigsrc3_clk);

    spwm->chip.dev = &pdev->dev;
    spwm->chip.ops = &sirf_pwm_ops;
    spwm->chip.base = 0;
    spwm->chip.npwm = 7;

    mutex_init(&spwm->mutex);

    ret = pwmchip_add(&spwm->chip);
    if (ret < 0) {
        dev_err(&pdev->dev, "failed to register PWM\n");
        goto err_pwmadd;
    }

    return 0;

err_pwmadd:
    clk_disable_unprepare(spwm->sigsrc3_clk);
err_src3_clk:
    clk_disable_unprepare(spwm->sigsrc0_clk);
err_src0_clk:
    clk_disable_unprepare(spwm->pwmc_clk);

    return ret;
}

static int sirf_pwm_remove(struct platform_device *pdev)
{
    struct sirf_pwm *spwm = platform_get_drvdata(pdev);

    clk_disable_unprepare(spwm->pwmc_clk);
    clk_disable_unprepare(spwm->sigsrc0_clk);
    clk_disable_unprepare(spwm->sigsrc3_clk);

    return pwmchip_remove(&spwm->chip);
}

#ifdef CONFIG_PM_SLEEP
static int sirf_pwm_suspend(struct device *dev)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct sirf_pwm *spwm = platform_get_drvdata(pdev);

    clk_disable_unprepare(spwm->pwmc_clk);

    return 0;
}

static void sirf_pwm_config_restore(struct sirf_pwm *spwm)
{
    struct pwm_device *pwm;
    int i;

    for (i = 0; i < spwm->chip.npwm; i++) {
        pwm = &spwm->chip.pwms[i];
        /*
         * while restoring from hibernation, state of pwm is enabled,
         * but PWM hardware is not re-enabled
         */
        if (test_bit(PWMF_REQUESTED, &pwm->flags) &&
            test_bit(PWMF_ENABLED, &pwm->flags))
            sirf_pwm_enable(&spwm->chip, pwm);
    }
}

static int sirf_pwm_resume(struct device *dev)
{
    struct sirf_pwm *spwm = dev_get_drvdata(dev);

    clk_prepare_enable(spwm->pwmc_clk);

    sirf_pwm_config_restore(spwm);

    return 0;
}

static int sirf_pwm_restore(struct device *dev)
{
    struct sirf_pwm *spwm = dev_get_drvdata(dev);

    /* back from hibernation, clock is already enabled */
    sirf_pwm_config_restore(spwm);

    return 0;
}

#else
#define sirf_pwm_resume NULL
#define sirf_pwm_suspend NULL
#define sirf_pwm_restore NULL
#endif

static const struct dev_pm_ops sirf_pwm_pm_ops = {
    .suspend = sirf_pwm_suspend,
    .resume = sirf_pwm_resume,
    .restore = sirf_pwm_restore,
};

static const struct of_device_id sirf_pwm_of_match[] = {
    { .compatible = "sirf,prima2-pwm", },
    {}
};
MODULE_DEVICE_TABLE(of, sirf_pwm_of_match);

static struct platform_driver sirf_pwm_driver = {
    .driver = {
        .name = "sirf-pwm",
        .pm = &sirf_pwm_pm_ops,
        .of_match_table = sirf_pwm_of_match,
    },
    .probe = sirf_pwm_probe,
    .remove = sirf_pwm_remove,
};

module_platform_driver(sirf_pwm_driver);

MODULE_DESCRIPTION("SIRF serial SoC PWM device core driver");
MODULE_AUTHOR("RongJun Ying <Rongjun.Ying at csr.com>");
MODULE_AUTHOR("Huayi Li <huayi.li at csr.com>");
MODULE_LICENSE("GPL v2");



More information about the linux-arm-kernel mailing list