pwm: atmel-hlcdc: add at91sam9x5 and sama5d3 errata handling
authorBoris BREZILLON <boris.brezillon@free-electrons.com>
Wed, 19 Nov 2014 14:33:09 +0000 (15:33 +0100)
committerThierry Reding <thierry.reding@gmail.com>
Thu, 4 Dec 2014 10:32:36 +0000 (11:32 +0100)
at91sam9x5 has an errata forbidding the use of slow clk as a clk source and
sama5d3 SoCs has another errata forbidding the use of div1 prescaler.

Take both of these erratas into account.

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Acked-by: Nicolas Ferre <nicolas.ferre@atmel.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
drivers/pwm/pwm-atmel-hlcdc.c

index eaf8b12..e7a785f 100644 (file)
 #define ATMEL_HLCDC_PWMPS_MAX          0x6
 #define ATMEL_HLCDC_PWMPS(x)           ((x) & ATMEL_HLCDC_PWMPS_MASK)
 
+struct atmel_hlcdc_pwm_errata {
+       bool slow_clk_erratum;
+       bool div1_clk_erratum;
+};
+
 struct atmel_hlcdc_pwm {
        struct pwm_chip chip;
        struct atmel_hlcdc *hlcdc;
        struct clk *cur_clk;
+       const struct atmel_hlcdc_pwm_errata *errata;
 };
 
 static inline struct atmel_hlcdc_pwm *to_atmel_hlcdc_pwm(struct pwm_chip *chip)
@@ -56,20 +62,29 @@ static int atmel_hlcdc_pwm_config(struct pwm_chip *c,
        u32 pwmcfg;
        int pres;
 
-       clk_freq = clk_get_rate(new_clk);
-       clk_period_ns = (u64)NSEC_PER_SEC * 256;
-       do_div(clk_period_ns, clk_freq);
+       if (!chip->errata || !chip->errata->slow_clk_erratum) {
+               clk_freq = clk_get_rate(new_clk);
+               clk_period_ns = (u64)NSEC_PER_SEC * 256;
+               do_div(clk_period_ns, clk_freq);
+       }
 
-       if (clk_period_ns > period_ns) {
+       /* Errata: cannot use slow clk on some IP revisions */
+       if ((chip->errata && chip->errata->slow_clk_erratum) ||
+           clk_period_ns > period_ns) {
                new_clk = hlcdc->sys_clk;
                clk_freq = clk_get_rate(new_clk);
                clk_period_ns = (u64)NSEC_PER_SEC * 256;
                do_div(clk_period_ns, clk_freq);
        }
 
-       for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++)
+       for (pres = 0; pres <= ATMEL_HLCDC_PWMPS_MAX; pres++) {
+               /* Errata: cannot divide by 1 on some IP revisions */
+               if (!pres && chip->errata && chip->errata->div1_clk_erratum)
+                       continue;
+
                if ((clk_period_ns << pres) >= period_ns)
                        break;
+       }
 
        if (pres > ATMEL_HLCDC_PWMPS_MAX)
                return -EINVAL;
@@ -187,8 +202,29 @@ static const struct pwm_ops atmel_hlcdc_pwm_ops = {
        .owner = THIS_MODULE,
 };
 
+static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_at91sam9x5_errata = {
+       .slow_clk_erratum = true,
+};
+
+static const struct atmel_hlcdc_pwm_errata atmel_hlcdc_pwm_sama5d3_errata = {
+       .div1_clk_erratum = true,
+};
+
+static const struct of_device_id atmel_hlcdc_dt_ids[] = {
+       {
+               .compatible = "atmel,at91sam9x5-hlcdc",
+               .data = &atmel_hlcdc_pwm_at91sam9x5_errata,
+       },
+       {
+               .compatible = "atmel,sama5d3-hlcdc",
+               .data = &atmel_hlcdc_pwm_sama5d3_errata,
+       },
+       { /* sentinel */ },
+};
+
 static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
 {
+       const struct of_device_id *match;
        struct device *dev = &pdev->dev;
        struct atmel_hlcdc_pwm *chip;
        struct atmel_hlcdc *hlcdc;
@@ -204,6 +240,10 @@ static int atmel_hlcdc_pwm_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
+       match = of_match_node(atmel_hlcdc_dt_ids, dev->parent->of_node);
+       if (match)
+               chip->errata = match->data;
+
        chip->hlcdc = hlcdc;
        chip->chip.ops = &atmel_hlcdc_pwm_ops;
        chip->chip.dev = dev;