Devicetree
 help / color / mirror / Atom feed
* Re: [PATCH v2 4/4] ARM: PWM: add allwinner sun8i pwm support.
From: Hao Zhang @ 2018-05-14 15:39 UTC (permalink / raw)
  To: André Przywara
  Cc: Thierry Reding, Maxime Ripard, robh+dt-DgEjT+Ai2ygdnm+yROfE0A,
	Mark Rutland, linux-I+IVW8TIWO2tmTQ+vhA3Yw, Chen-Yu Tsai,
	Claudiu Beznea, linux-gpio-u79uwXL29TY76Z2rM5mHXA, open list,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	moderated list:ARM/Allwinner sunXi SoC support,
	linux-pwm-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <8bd4247f-45fd-1385-3ba2-accd7a1e7eb9-5wv7dgnIgG8@public.gmane.org>

2018-02-28 9:55 GMT+08:00 André Przywara <andre.przywara-5wv7dgnIgG8@public.gmane.org>:
> Hi,
>
> On 25/02/18 13:53, hao_zhang wrote:
>> This patch add allwinner sun8i pwm support.
>
> Again, the subject line is too generic. Mention the R40?
>
> Can you elaborate here a bit? Mention that is used on the R40, but not
> other sun8i SoCs, for instance. And mention that this is very different
> from the sun4i-pwm device, so justifies a new driver. Possibly mention
> some features? And that we for now just implement a subset of them.

Thanks for reviews, elaborate it next patch:)

>
>>
>> Signed-off-by: hao_zhang <hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> ---
>>  drivers/pwm/Kconfig     |  10 ++
>>  drivers/pwm/Makefile    |   1 +
>>  drivers/pwm/pwm-sun8i.c | 401 ++++++++++++++++++++++++++++++++++++++++++++++++
>
> I am not too happy with this name, but I guess there are no better
> alternatives, so it's probably OK to keep it.
>
>>  3 files changed, 412 insertions(+)
>>  create mode 100644 drivers/pwm/pwm-sun8i.c
>>
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index 763ee50..7e68d0f 100644
>> --- a/drivers/pwm/Kconfig
>> +++ b/drivers/pwm/Kconfig
>> @@ -444,6 +444,16 @@ config PWM_SUN4I
>>         To compile this driver as a module, choose M here: the module
>>         will be called pwm-sun4i.
>>
>> +config PWM_SUN8I
>> +     tristate "Allwinner PWM SUN8I support"
>> +     depends on ARCH_SUNXI || COMPILE_TEST
>> +     depends on HAS_IOMEM && COMMON_CLK
>> +     help
>> +       Generic PWM framework driver for Allwinner SoCs.
>
> Mmh, not really. So far there is only one SoC using this. Maybe:
>           Driver for the enhanced PWM IP used in some newer Allwinner
>           SoCs.
>
>> +
>> +       To compile this driver as a module, choose M here: the module
>> +       will be called pwm-sun8i.
>> +
>>  config PWM_TEGRA
>>       tristate "NVIDIA Tegra PWM support"
>>       depends on ARCH_TEGRA
>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>> index 0258a74..cd6bf40 100644
>> --- a/drivers/pwm/Makefile
>> +++ b/drivers/pwm/Makefile
>> @@ -44,6 +44,7 @@ obj-$(CONFIG_PWM_STM32)             += pwm-stm32.o
>>  obj-$(CONFIG_PWM_STM32_LP)   += pwm-stm32-lp.o
>>  obj-$(CONFIG_PWM_STMPE)              += pwm-stmpe.o
>>  obj-$(CONFIG_PWM_SUN4I)              += pwm-sun4i.o
>> +obj-$(CONFIG_PWM_SUN8I)              += pwm-sun8i.o
>>  obj-$(CONFIG_PWM_TEGRA)              += pwm-tegra.o
>>  obj-$(CONFIG_PWM_TIECAP)     += pwm-tiecap.o
>>  obj-$(CONFIG_PWM_TIEHRPWM)   += pwm-tiehrpwm.o
>> diff --git a/drivers/pwm/pwm-sun8i.c b/drivers/pwm/pwm-sun8i.c
>> new file mode 100644
>> index 0000000..cf23b0a
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-sun8i.c
>> @@ -0,0 +1,401 @@
>> +#include <linux/bitops.h>
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/err.h>
>> +#include <linux/io.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/of_device.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pwm.h>
>> +#include <linux/slab.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/time.h>
>> +#include <linux/regmap.h>
>> +
>> +#define PWM_IRQ_ENABLE_REG   0x0000
>> +#define PCIE(ch)     BIT(ch)
>
> Can you please align those:
> #define PWM_IRQ_ENABLE_REG      0x0000
> #define PCIE(ch)                BIT(ch)
>
> And all those below as well? Which means you might want to insert
> another tab to cater for those longer symbols.

yep, align it is batter :-)

>
>> +
>> +#define PWM_IRQ_STATUS_REG   0x0004
>> +#define PIS(ch)      BIT(ch)
>> +
>> +#define CAPTURE_IRQ_ENABLE_REG       0x0010
>> +#define CFIE(ch)     BIT(ch << 1 + 1)
>> +#define CRIE(ch)     BIT(ch << 1)
>> +
>> +#define CAPTURE_IRQ_STATUS_REG       0x0014
>> +#define CFIS(ch)     BIT(ch << 1 + 1)
>> +#define CRIS(ch)     BIT(ch << 1)
>> +
>> +#define CLK_CFG_REG(ch)      (0x0020 + (ch >> 1) * 4)
>> +#define CLK_SRC      BIT(7)
>> +#define CLK_SRC_BYPASS_SEC   BIT(6)
>> +#define CLK_SRC_BYPASS_FIR   BIT(5)
>> +#define CLK_GATING   BIT(4)
>> +#define CLK_DIV_M    GENMASK(3, 0)
>> +
>> +#define PWM_DZ_CTR_REG(ch)   (0x0030 + (ch >> 1) * 4)
>> +#define PWM_DZ_INTV  GENMASK(15, 8)
>> +#define PWM_DZ_EN    BIT(0)
>> +
>> +#define PWM_ENABLE_REG       0x0040
>> +#define PWM_EN(ch)   BIT(ch)
>> +
>> +#define CAPTURE_ENABLE_REG   0x0044
>> +#define CAP_EN(ch)   BIT(ch)
>> +
>> +#define PWM_CTR_REG(ch)      (0x0060 + ch * 0x20)
>> +#define PWM_PERIOD_RDY       BIT(11)
>> +#define PWM_PUL_START        BIT(10)
>> +#define PWM_MODE     BIT(9)
>> +#define PWM_ACT_STA  BIT(8)
>> +#define PWM_PRESCAL_K        GENMASK(7, 0)
>> +
>> +#define PWM_PERIOD_REG(ch)   (0x0064 + ch * 0x20)
>> +#define PWM_ENTIRE_CYCLE     GENMASK(31, 16)
>> +#define PWM_ACT_CYCLE        GENMASK(15, 0)
>> +
>> +#define PWM_CNT_REG(ch)      (0x0068 + ch * 0x20)
>> +#define PWM_CNT_VAL  GENMASK(15, 0)
>> +
>> +#define CAPTURE_CTR_REG(ch)  (0x006c + ch * 0x20)
>> +#define CAPTURE_CRLF BIT(2)
>> +#define CAPTURE_CFLF BIT(1)
>> +#define CAPINV       BIT(0)
>> +
>> +#define CAPTURE_RISE_REG(ch) (0x0070 + ch * 0x20)
>> +#define CAPTURE_CRLR GENMASK(15, 0)
>> +
>> +#define CAPTURE_FALL_REG(ch) (0x0074 + ch * 0x20)
>> +#define CAPTURE_CFLR GENMASK(15, 0)
>> +
>> +struct sun8i_pwm_data {
>> +     bool has_prescaler_bypass;
>> +     bool has_rdy;
>> +     unsigned int npwm;
>> +};
>
> I believe you don't need this structure. See below.

yep, clock will output directly while bypass has been set,
and equivalent to 50% duty cycles...

>
>> +
>> +struct sun8i_pwm_chip {
>> +     struct pwm_chip chip;
>> +     struct clk *clk;
>> +     void __iomem *base;
>> +     spinlock_t ctrl_lock;
>> +     const struct sun8i_pwm_data *data;
>> +     struct regmap *regmap;
>> +};
>> +
>> +static const u16 div_m_table[] = {
>> +     1,
>> +     2,
>> +     4,
>> +     8,
>> +     16,
>> +     32,
>> +     64,
>> +     128,
>> +     256
>> +};
>
> That looks very much like: "1U << x" to me.

uhmm, i think using table is more explicit and extended...

>
>> +
>> +static inline struct sun8i_pwm_chip *to_sun8i_pwm_chip(struct pwm_chip *chip)
>
> No need for "inline", the compiler knows better. static is enough.

okey :-)

>
>> +{
>> +     return container_of(chip, struct sun8i_pwm_chip, chip);
>> +}
>> +
>> +static u32 sun8i_pwm_read(struct sun8i_pwm_chip *sun8i_pwm,
>> +             unsigned long offset)
>
> Can you please align those continuation lines properly? The first
> character in the new line should be aligned to the first character of
> the first argument. Use tabs first, then fill up with spaces:

Align it next :-)

>
> static u32 sun8i_pwm_read(struct sun8i_pwm_chip *sun8i_pwm,
>                           unsigned long offset)
>
> This applies to the rest of the file as well.
>
>> +{
>> +     u32 val;
>> +
>> +     regmap_read(sun8i_pwm->regmap, offset, &val);
>> +
>> +     return val;
>> +}
>> +
>> +static inline void sun8i_pwm_set_bit(struct sun8i_pwm_chip *sun8i_pwm,
>
> no inline (for those below as well)
>
>> +             unsigned long reg, u32 bit)
>> +{
>> +     regmap_update_bits(sun8i_pwm->regmap, reg, bit, bit);
>> +}
>> +
>> +static inline void sun8i_pwm_clear_bit(struct sun8i_pwm_chip *sun8i_pwm,
>> +             unsigned long reg, u32 bit)
>> +{
>> +     regmap_update_bits(sun8i_pwm->regmap, reg, bit, 0);
>> +}
>> +
>> +static inline void sun8i_pwm_set_value(struct sun8i_pwm_chip *sun8i_pwm,
>> +             unsigned long reg, u32 mask, u32 val)
>> +{
>> +     regmap_update_bits(sun8i_pwm->regmap, reg, mask, val);
>> +}
>> +
>> +static void sun8i_pwm_set_polarity(struct sun8i_pwm_chip *chip, u32 ch,
>> +             enum pwm_polarity polarity)
>> +{
>> +     if (polarity == PWM_POLARITY_NORMAL)
>> +             sun8i_pwm_set_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
>> +     else
>> +             sun8i_pwm_clear_bit(chip, PWM_CTR_REG(ch), PWM_ACT_STA);
>> +}
>> +
>> +static int sun8i_pwm_config(struct sun8i_pwm_chip *sun8i_pwm, u8 ch,
>> +             struct pwm_state *state)
>> +{
>> +     u64 clk_rate, clk_div, val;
>> +     u16 prescaler = 0;
>> +     u8 id = 0;
>> +
>> +     clk_rate = clk_get_rate(sun8i_pwm->clk);
>> +
>> +     if (clk_rate == 24000000)
>> +             sun8i_pwm_clear_bit(sun8i_pwm, CLK_CFG_REG(ch), CLK_SRC);
>> +     else
>> +             sun8i_pwm_set_bit(sun8i_pwm, CLK_CFG_REG(ch), CLK_SRC);
>
> This hardcoded 24MHz looks slightly dodgy and should be replaced with
> some proper code to select the best matching clock, out of a number of
> them given in the DT (see the DT binding mail).
> Without thinking too deeply about it, I guess we try which clocks gives
> the least error for the given configuration. The frequency alone might
> be a good first guide.
> If you can't be bothered with coding this, we might just go ahead with
> the first specified clock and always use this, for now.

Dose the framework support parse 2 or more clk from DT ?
yep, It is better to set the clk automatically

>
>> +
>> +     if (sun8i_pwm->data->has_prescaler_bypass) {
>
> What is this about? I think this is a misunderstanding:
> The bypass bits allows to directly pass on the input clock to the output
> pin, without any actual PWM properties. So if one channel is (by
> chance?) configured for a 50% duty cycle and the same frequency as one
> of the input clocks, you might want to use the bypass bit instead. But I
> don't see many advantages in doing so, so I guess we can ignore it in a
> generic PWM driver.
> Anyway using some hardcoded value from the "data" structure looks just
> wrong to me. I guess you can just remove this, along with the
> has_prescaler_bypass variable from the sun8i_pwm_data structure.

Agree to remove it.

>
>> +             /* pwm output bypass */
>> +             if (ch % 2)
>> +                     sun8i_pwm_set_bit(sun8i_pwm, CLK_CFG_REG(ch),
>> +                                     CLK_SRC_BYPASS_FIR);
>> +             else
>> +                     sun8i_pwm_set_bit(sun8i_pwm, CLK_CFG_REG(ch),
>> +                                     CLK_SRC_BYPASS_SEC);
>> +             return 0;
>> +     }
>> +
>> +     val = state->period * clk_rate;
>> +     do_div(val, NSEC_PER_SEC);
>> +     if (val < 1) {
>> +             dev_err(sun8i_pwm->chip.dev,
>> +                             "Period expects a larger value\n");
>
> Alignment.
> And you might want to hook in here to select a higher frequency input clock.
>
>> +             return -EINVAL;
>> +     }
>> +
>> +     /* calculate and set prescalar, div table, pwn entrie cycle */
>
>                              prescaler             PWM entire
>
> though I believe this "entire cycle" term is an Allwinner invention.
> Wouldn't period be a better term here, also matching the framework?

It seem no...
referent the manual,  "entire cycle" seem means the count of
prescaler_clk(divide by prescaler),
you shoule multiply Tprescaler_clk, then is Tperiod.

>
>> +     clk_div = val;
>> +
>> +     while (clk_div > 65535) {
>> +             prescaler++;
>> +             clk_div = val;
>> +             do_div(clk_div, prescaler + 1);
>> +             do_div(clk_div, div_m_table[id]);
>
>                                 1U << id
>
>> +
>> +             if (prescaler == 255) {
>> +                     prescaler = 0;
>> +                     id++;
>> +                     if (id == 9)
>> +                             return -EINVAL;
>> +             }
>> +     }
>> +
>> +     sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
>> +                     PWM_ENTIRE_CYCLE, clk_div << 16);
>> +     sun8i_pwm_set_value(sun8i_pwm, PWM_CTR_REG(ch),
>> +                     PWM_PRESCAL_K, prescaler << 0);
>> +     sun8i_pwm_set_value(sun8i_pwm, CLK_CFG_REG(ch),
>> +                     CLK_DIV_M, id << 0);
>> +
>> +     /* set duty cycle */
>> +     val = (prescaler + 1) * div_m_table[id] * clk_div;
>
>                                 (1U << id)
>
> You might want to check for the range, though.

Yep :-)

>
>> +     val = state->period;
>> +     do_div(val, clk_div);
>> +     clk_div = state->duty_cycle;
>> +     do_div(clk_div, val);
>> +
>> +     sun8i_pwm_set_value(sun8i_pwm, PWM_PERIOD_REG(ch),
>> +                     PWM_ACT_CYCLE, clk_div << 0);
>> +
>> +     return 0;
>> +}
>> +
>> +static int sun8i_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>> +             struct pwm_state *state)
>> +{
>> +     int ret;
>> +     struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
>> +     struct pwm_state cstate;
>> +
>> +     pwm_get_state(pwm, &cstate);
>> +     if (!cstate.enabled) {
>> +             ret = clk_prepare_enable(sun8i_pwm->clk);
>> +             if (ret) {
>> +                     dev_err(chip->dev, "Failed to enable PWM clock\n");
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     spin_lock(&sun8i_pwm->ctrl_lock);
>> +
>> +     if ((cstate.period != state->period) ||
>> +                     (cstate.duty_cycle != state->duty_cycle)) {
>> +             ret = sun8i_pwm_config(sun8i_pwm, pwm->hwpwm, state);
>> +             if (ret) {
>> +                     spin_unlock(&sun8i_pwm->ctrl_lock);
>> +                     dev_err(chip->dev, "Failed to config PWM\n");
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     if (state->polarity != cstate.polarity)
>> +             sun8i_pwm_set_polarity(sun8i_pwm, pwm->hwpwm, state->polarity);
>> +
>> +     if (state->enabled) {
>> +             sun8i_pwm_set_bit(sun8i_pwm,
>> +                             CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
>> +
>> +             sun8i_pwm_set_bit(sun8i_pwm,
>> +                             PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
>> +     } else {
>> +             sun8i_pwm_clear_bit(sun8i_pwm,
>> +                             CLK_CFG_REG(pwm->hwpwm), CLK_GATING);
>> +
>> +             sun8i_pwm_clear_bit(sun8i_pwm,
>> +                             PWM_ENABLE_REG, PWM_EN(pwm->hwpwm));
>> +     }
>> +
>> +     spin_unlock(&sun8i_pwm->ctrl_lock);
>> +
>> +     return 0;
>> +}
>> +
>> +static void sun8i_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>> +             struct pwm_state *state)
>> +{
>> +     struct sun8i_pwm_chip *sun8i_pwm = to_sun8i_pwm_chip(chip);
>> +     u64 clk_rate, tmp;
>> +     u32 val;
>> +     u16 clk_div, act_cycle;
>> +     u8 prescal, id;
>
> You might want to add a channel variable to increase readability:
>         int channel = pwm->hwpwm;
>

Okey

>> +
>> +     clk_rate = clk_get_rate(sun8i_pwm->clk);
>> +
>> +     val = sun8i_pwm_read(sun8i_pwm, PWM_CTR_REG(pwm->hwpwm));
>> +     if (PWM_ACT_STA & val)
>> +             state->polarity = PWM_POLARITY_NORMAL;
>> +     else
>> +             state->polarity = PWM_POLARITY_INVERSED;
>> +
>> +     prescal = PWM_PRESCAL_K & val;
>> +
>> +     val = sun8i_pwm_read(sun8i_pwm, PWM_ENABLE_REG);
>> +     if (PWM_EN(pwm->hwpwm) & val)
>> +             state->enabled = true;
>> +     else
>> +             state->enabled = false;
>> +
>> +     val = sun8i_pwm_read(sun8i_pwm, PWM_PERIOD_REG(pwm->hwpwm));
>> +     act_cycle = PWM_ACT_CYCLE & val;
>> +     clk_div = val >> 16;
>> +
>> +     val = sun8i_pwm_read(sun8i_pwm, CLK_CFG_REG(pwm->hwpwm));
>> +     id = CLK_DIV_M & val;
>> +
>> +     tmp = act_cycle * prescal * div_m_table[id] * NSEC_PER_SEC;
>> +     state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
>> +     tmp = clk_div * prescal * div_m_table[id] * NSEC_PER_SEC;
>> +     state->period = DIV_ROUND_CLOSEST_ULL(tmp, clk_rate);
>> +}
>> +
>> +static const struct regmap_config sun8i_pwm_regmap_config = {
>> +     .reg_bits = 32,
>> +     .reg_stride = 4,
>> +     .val_bits = 32,
>> +     .max_register = CAPTURE_FALL_REG(7),
>> +};
>> +
>> +static const struct pwm_ops sun8i_pwm_ops = {
>> +     .apply = sun8i_pwm_apply,
>> +     .get_state = sun8i_pwm_get_state,
>> +     .owner = THIS_MODULE,
>> +};
>> +
>> +static const struct sun8i_pwm_data sun8i_pwm_data_r40 = {
>> +     .has_prescaler_bypass = false,
>
> This is not needed (see my comment above).

yep.

>
>> +     .has_rdy = true,
>
> And this is not used. Copied from sun4i? Where it interestingly isn't
> used either ;-)
>
>> +     .npwm = 8,
>
> I would really love to see this being moved to the DT (see my other mail
> to Thierry about the generic property).
>
> This would mean you don't need a SoC specific structure at all.

okey.

>
>> +};
>> +
>> +static const struct of_device_id sun8i_pwm_dt_ids[] = {
>> +     {
>> +             .compatible = "allwinner,sun8i-r40-pwm",
>> +             .data = &sun8i_pwm_data_r40,
>> +     },
>> +     {},
>> +};
>> +MODULE_DEVICE_TABLE(of, sun8i_pwm_dt_ids);
>> +
>> +static int sun8i_pwm_probe(struct platform_device *pdev)
>> +{
>> +     struct sun8i_pwm_chip *pwm;
>> +     struct resource *res;
>> +     int ret;
>> +     const struct of_device_id *match;
>> +
>> +     match = of_match_device(sun8i_pwm_dt_ids, &pdev->dev);
>> +     if (!match) {
>> +             dev_err(&pdev->dev, "Error: No device match found\n");
>> +             return -ENODEV;
>> +     }
>> +
>> +     pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
>> +     if (!pwm)
>> +             return -ENOMEM;
>> +
>> +     res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +     pwm->base = devm_ioremap_resource(&pdev->dev, res);
>> +     if (IS_ERR(pwm->base))
>> +             return PTR_ERR(pwm->base);
>> +
>> +     pwm->regmap = devm_regmap_init_mmio(&pdev->dev, pwm->base,
>> +                     &sun8i_pwm_regmap_config);
>> +     if (IS_ERR(pwm->regmap)) {
>> +             dev_err(&pdev->dev, "Failed to create regmap\n");
>> +             return PTR_ERR(pwm->regmap);
>> +     }
>> +
>> +     pwm->clk = devm_clk_get(&pdev->dev, NULL);
>> +     if (IS_ERR(pwm->clk))
>> +             return PTR_ERR(pwm->clk);
>
> This would need to be extended to get multiple clocks.

okey.

>
>> +
>> +     pwm->data = match->data;
>> +     pwm->chip.dev = &pdev->dev;
>> +     pwm->chip.ops = &sun8i_pwm_ops;
>> +     pwm->chip.base = -1;
>> +     pwm->chip.npwm = pwm->data->npwm;
>
> It should be fairly easy to initialise this from some DT property.
>
> That's it for the my first review round. Haven't checked the actual
> algorithm and bit assignments yet.
> Did you manage to test this?

Sure :-)
All has been tested on my T3 board (compatible V40, R40)
PWM signal is work well observe from oscilloscope.

>
> Cheers,
> Andre.
>
>> +     pwm->chip.of_xlate = of_pwm_xlate_with_flags;
>> +     pwm->chip.of_pwm_n_cells = 3;
>> +
>> +     spin_lock_init(&pwm->ctrl_lock);
>> +
>> +     ret = pwmchip_add(&pwm->chip);
>> +     if (ret < 0) {
>> +             dev_err(&pdev->dev, "Failed to add PWM chip: %d\n", ret);
>> +             return ret;
>> +     }
>> +
>> +     platform_set_drvdata(pdev, pwm);
>> +
>> +     return 0;
>> +}
>> +
>> +static int sun8i_pwm_remove(struct platform_device *pdev)
>> +{
>> +     struct sun8i_pwm_chip *pwm = platform_get_drvdata(pdev);
>> +
>> +     return pwmchip_remove(&pwm->chip);
>> +}
>> +
>> +static struct platform_driver sun8i_pwm_driver = {
>> +     .driver = {
>> +             .name = "sun8i-pwm",
>> +             .of_match_table = sun8i_pwm_dt_ids,
>> +     },
>> +     .probe = sun8i_pwm_probe,
>> +     .remove = sun8i_pwm_remove,
>> +};
>> +module_platform_driver(sun8i_pwm_driver);
>> +
>> +MODULE_ALIAS("platform: sun8i-pwm");
>> +MODULE_AUTHOR("Hao Zhang <hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>");
>> +MODULE_DESCRIPTION("Allwinner sun8i PWM driver");
>> +MODULE_LICENSE("GPL v2");
>>
>

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* Re: [v4,2/2] mailbox: add STMicroelectronics STM32 IPCC driver
From: Fabien DESSENNE @ 2018-05-14 15:54 UTC (permalink / raw)
  To: Rob Herring, Mark Rutland, Maxime Coquelin, Alexandre TORGUE,
	Jassi Brar, Ludovic BARRE, devicetree@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org
  Cc: Arnaud POULIQUEN, Benjamin Gaignard, Loic PALLARDY
In-Reply-To: <1523515661-8318-3-git-send-email-fabien.dessenne@st.com>

Hi Jassi,

Do you have any more comments or do you plan to have this patch part of 
the 4.18 pull request ?

BR

Fabien


On 12/04/18 08:47, Fabien DESSENNE wrote:
> The STMicroelectronics STM32 Inter-Processor Communication Controller
> (IPCC) is used for communicating data between two processors.
> It provides a non blocking signaling mechanism to post and retrieve
> communication data in an atomic way.
>
> Signed-off-by: Fabien Dessenne <fabien.dessenne@st.com>
> Signed-off-by: Ludovic Barre <ludovic.barre@st.com>
> ---
>   drivers/mailbox/Kconfig      |   8 +
>   drivers/mailbox/Makefile     |   2 +
>   drivers/mailbox/stm32-ipcc.c | 402 +++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 412 insertions(+)
>   create mode 100644 drivers/mailbox/stm32-ipcc.c
>
> diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
> index ba2f152..d7581f0 100644
> --- a/drivers/mailbox/Kconfig
> +++ b/drivers/mailbox/Kconfig
> @@ -171,4 +171,12 @@ config BCM_FLEXRM_MBOX
>   	  Mailbox implementation of the Broadcom FlexRM ring manager,
>   	  which provides access to various offload engines on Broadcom
>   	  SoCs. Say Y here if you want to use the Broadcom FlexRM.
> +
> +config STM32_IPCC
> +	tristate "STM32 IPCC Mailbox"
> +	depends on MACH_STM32MP157
> +	help
> +	  Mailbox implementation for STMicroelectonics STM32 family chips
> +	  with hardware for Inter-Processor Communication Controller (IPCC)
> +	  between processors. Say Y here if you want to have this support.
>   endif
> diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
> index 4896f8d..7ea9654 100644
> --- a/drivers/mailbox/Makefile
> +++ b/drivers/mailbox/Makefile
> @@ -36,3 +36,5 @@ obj-$(CONFIG_BCM_FLEXRM_MBOX)	+= bcm-flexrm-mailbox.o
>   obj-$(CONFIG_QCOM_APCS_IPC)	+= qcom-apcs-ipc-mailbox.o
>   
>   obj-$(CONFIG_TEGRA_HSP_MBOX)	+= tegra-hsp.o
> +
> +obj-$(CONFIG_STM32_IPCC) 	+= stm32-ipcc.o
> diff --git a/drivers/mailbox/stm32-ipcc.c b/drivers/mailbox/stm32-ipcc.c
> new file mode 100644
> index 0000000..533b0da
> --- /dev/null
> +++ b/drivers/mailbox/stm32-ipcc.c
> @@ -0,0 +1,402 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) STMicroelectronics 2018 - All Rights Reserved
> + * Authors: Ludovic Barre <ludovic.barre@st.com> for STMicroelectronics.
> + *          Fabien Dessenne <fabien.dessenne@st.com> for STMicroelectronics.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/clk.h>
> +#include <linux/interrupt.h>
> +#include <linux/mailbox_controller.h>
> +#include <linux/module.h>
> +#include <linux/of_irq.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_wakeirq.h>
> +
> +#define IPCC_XCR		0x000
> +#define XCR_RXOIE		BIT(0)
> +#define XCR_TXOIE		BIT(16)
> +
> +#define IPCC_XMR		0x004
> +#define IPCC_XSCR		0x008
> +#define IPCC_XTOYSR		0x00c
> +
> +#define IPCC_PROC_OFFST		0x010
> +
> +#define IPCC_HWCFGR		0x3f0
> +#define IPCFGR_CHAN_MASK	GENMASK(7, 0)
> +
> +#define IPCC_VER		0x3f4
> +#define VER_MINREV_MASK		GENMASK(3, 0)
> +#define VER_MAJREV_MASK		GENMASK(7, 4)
> +
> +#define RX_BIT_MASK		GENMASK(15, 0)
> +#define RX_BIT_CHAN(chan)	BIT(chan)
> +#define TX_BIT_SHIFT		16
> +#define TX_BIT_MASK		GENMASK(31, 16)
> +#define TX_BIT_CHAN(chan)	BIT(TX_BIT_SHIFT + (chan))
> +
> +#define STM32_MAX_PROCS		2
> +
> +enum {
> +	IPCC_IRQ_RX,
> +	IPCC_IRQ_TX,
> +	IPCC_IRQ_NUM,
> +};
> +
> +struct stm32_ipcc {
> +	struct mbox_controller controller;
> +	void __iomem *reg_base;
> +	void __iomem *reg_proc;
> +	struct clk *clk;
> +	int irqs[IPCC_IRQ_NUM];
> +	int wkp;
> +	u32 proc_id;
> +	u32 n_chans;
> +	u32 xcr;
> +	u32 xmr;
> +};
> +
> +static inline void stm32_ipcc_set_bits(void __iomem *reg, u32 mask)
> +{
> +	writel_relaxed(readl_relaxed(reg) | mask, reg);
> +}
> +
> +static inline void stm32_ipcc_clr_bits(void __iomem *reg, u32 mask)
> +{
> +	writel_relaxed(readl_relaxed(reg) & ~mask, reg);
> +}
> +
> +static irqreturn_t stm32_ipcc_rx_irq(int irq, void *data)
> +{
> +	struct stm32_ipcc *ipcc = data;
> +	struct device *dev = ipcc->controller.dev;
> +	u32 status, mr, tosr, chan;
> +	irqreturn_t ret = IRQ_NONE;
> +	int proc_offset;
> +
> +	/* read 'channel occupied' status from other proc */
> +	proc_offset = ipcc->proc_id ? -IPCC_PROC_OFFST : IPCC_PROC_OFFST;
> +	tosr = readl_relaxed(ipcc->reg_proc + proc_offset + IPCC_XTOYSR);
> +	mr = readl_relaxed(ipcc->reg_proc + IPCC_XMR);
> +
> +	/* search for unmasked 'channel occupied' */
> +	status = tosr & FIELD_GET(RX_BIT_MASK, ~mr);
> +
> +	for (chan = 0; chan < ipcc->n_chans; chan++) {
> +		if (!(status & (1 << chan)))
> +			continue;
> +
> +		dev_dbg(dev, "%s: chan:%d rx\n", __func__, chan);
> +
> +		mbox_chan_received_data(&ipcc->controller.chans[chan], NULL);
> +
> +		stm32_ipcc_set_bits(ipcc->reg_proc + IPCC_XSCR,
> +				    RX_BIT_CHAN(chan));
> +
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	return ret;
> +}
> +
> +static irqreturn_t stm32_ipcc_tx_irq(int irq, void *data)
> +{
> +	struct stm32_ipcc *ipcc = data;
> +	struct device *dev = ipcc->controller.dev;
> +	u32 status, mr, tosr, chan;
> +	irqreturn_t ret = IRQ_NONE;
> +
> +	tosr = readl_relaxed(ipcc->reg_proc + IPCC_XTOYSR);
> +	mr = readl_relaxed(ipcc->reg_proc + IPCC_XMR);
> +
> +	/* search for unmasked 'channel free' */
> +	status = ~tosr & FIELD_GET(TX_BIT_MASK, ~mr);
> +
> +	for (chan = 0; chan < ipcc->n_chans ; chan++) {
> +		if (!(status & (1 << chan)))
> +			continue;
> +
> +		dev_dbg(dev, "%s: chan:%d tx\n", __func__, chan);
> +
> +		/* mask 'tx channel free' interrupt */
> +		stm32_ipcc_set_bits(ipcc->reg_proc + IPCC_XMR,
> +				    TX_BIT_CHAN(chan));
> +
> +		mbox_chan_txdone(&ipcc->controller.chans[chan], 0);
> +
> +		ret = IRQ_HANDLED;
> +	}
> +
> +	return ret;
> +}
> +
> +static int stm32_ipcc_send_data(struct mbox_chan *link, void *data)
> +{
> +	unsigned int chan = (unsigned int)link->con_priv;
> +	struct stm32_ipcc *ipcc = container_of(link->mbox, struct stm32_ipcc,
> +					       controller);
> +
> +	dev_dbg(ipcc->controller.dev, "%s: chan:%d\n", __func__, chan);
> +
> +	/* set channel n occupied */
> +	stm32_ipcc_set_bits(ipcc->reg_proc + IPCC_XSCR, TX_BIT_CHAN(chan));
> +
> +	/* unmask 'tx channel free' interrupt */
> +	stm32_ipcc_clr_bits(ipcc->reg_proc + IPCC_XMR, TX_BIT_CHAN(chan));
> +
> +	return 0;
> +}
> +
> +static int stm32_ipcc_startup(struct mbox_chan *link)
> +{
> +	unsigned int chan = (unsigned int)link->con_priv;
> +	struct stm32_ipcc *ipcc = container_of(link->mbox, struct stm32_ipcc,
> +					       controller);
> +	int ret;
> +
> +	ret = clk_prepare_enable(ipcc->clk);
> +	if (ret) {
> +		dev_err(ipcc->controller.dev, "can not enable the clock\n");
> +		return ret;
> +	}
> +
> +	/* unmask 'rx channel occupied' interrupt */
> +	stm32_ipcc_clr_bits(ipcc->reg_proc + IPCC_XMR, RX_BIT_CHAN(chan));
> +
> +	return 0;
> +}
> +
> +static void stm32_ipcc_shutdown(struct mbox_chan *link)
> +{
> +	unsigned int chan = (unsigned int)link->con_priv;
> +	struct stm32_ipcc *ipcc = container_of(link->mbox, struct stm32_ipcc,
> +					       controller);
> +
> +	/* mask rx/tx interrupt */
> +	stm32_ipcc_set_bits(ipcc->reg_proc + IPCC_XMR,
> +			    RX_BIT_CHAN(chan) | TX_BIT_CHAN(chan));
> +
> +	clk_disable_unprepare(ipcc->clk);
> +}
> +
> +static const struct mbox_chan_ops stm32_ipcc_ops = {
> +	.send_data	= stm32_ipcc_send_data,
> +	.startup	= stm32_ipcc_startup,
> +	.shutdown	= stm32_ipcc_shutdown,
> +};
> +
> +static int stm32_ipcc_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct device_node *np = dev->of_node;
> +	struct stm32_ipcc *ipcc;
> +	struct resource *res;
> +	unsigned int i;
> +	int ret;
> +	u32 ip_ver;
> +	static const char * const irq_name[] = {"rx", "tx"};
> +	irq_handler_t irq_thread[] = {stm32_ipcc_rx_irq, stm32_ipcc_tx_irq};
> +
> +	if (!np) {
> +		dev_err(dev, "No DT found\n");
> +		return -ENODEV;
> +	}
> +
> +	ipcc = devm_kzalloc(dev, sizeof(*ipcc), GFP_KERNEL);
> +	if (!ipcc)
> +		return -ENOMEM;
> +
> +	/* proc_id */
> +	if (of_property_read_u32(np, "st,proc-id", &ipcc->proc_id)) {
> +		dev_err(dev, "Missing st,proc-id\n");
> +		return -ENODEV;
> +	}
> +
> +	if (ipcc->proc_id >= STM32_MAX_PROCS) {
> +		dev_err(dev, "Invalid proc_id (%d)\n", ipcc->proc_id);
> +		return -EINVAL;
> +	}
> +
> +	/* regs */
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	ipcc->reg_base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(ipcc->reg_base))
> +		return PTR_ERR(ipcc->reg_base);
> +
> +	ipcc->reg_proc = ipcc->reg_base + ipcc->proc_id * IPCC_PROC_OFFST;
> +
> +	/* clock */
> +	ipcc->clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(ipcc->clk))
> +		return PTR_ERR(ipcc->clk);
> +
> +	ret = clk_prepare_enable(ipcc->clk);
> +	if (ret) {
> +		dev_err(dev, "can not enable the clock\n");
> +		return ret;
> +	}
> +
> +	/* irq */
> +	for (i = 0; i < IPCC_IRQ_NUM; i++) {
> +		ipcc->irqs[i] = of_irq_get_byname(dev->of_node, irq_name[i]);
> +		if (ipcc->irqs[i] < 0) {
> +			dev_err(dev, "no IRQ specified %s\n", irq_name[i]);
> +			ret = ipcc->irqs[i];
> +			goto err_clk;
> +		}
> +
> +		ret = devm_request_threaded_irq(dev, ipcc->irqs[i], NULL,
> +						irq_thread[i], IRQF_ONESHOT,
> +						dev_name(dev), ipcc);
> +		if (ret) {
> +			dev_err(dev, "failed to request irq %d (%d)\n", i, ret);
> +			goto err_clk;
> +		}
> +	}
> +
> +	/* mask and enable rx/tx irq */
> +	stm32_ipcc_set_bits(ipcc->reg_proc + IPCC_XMR,
> +			    RX_BIT_MASK | TX_BIT_MASK);
> +	stm32_ipcc_set_bits(ipcc->reg_proc + IPCC_XCR, XCR_RXOIE | XCR_TXOIE);
> +
> +	/* wakeup */
> +	if (of_property_read_bool(np, "wakeup-source")) {
> +		ipcc->wkp = of_irq_get_byname(dev->of_node, "wakeup");
> +		if (ipcc->wkp < 0) {
> +			dev_err(dev, "could not get wakeup IRQ\n");
> +			ret = ipcc->wkp;
> +			goto err_clk;
> +		}
> +
> +		device_init_wakeup(dev, true);
> +		ret = dev_pm_set_dedicated_wake_irq(dev, ipcc->wkp);
> +		if (ret) {
> +			dev_err(dev, "Failed to set wake up irq\n");
> +			goto err_init_wkp;
> +		}
> +	} else {
> +		device_init_wakeup(dev, false);
> +	}
> +
> +	/* mailbox controller */
> +	ipcc->n_chans = readl_relaxed(ipcc->reg_base + IPCC_HWCFGR);
> +	ipcc->n_chans &= IPCFGR_CHAN_MASK;
> +
> +	ipcc->controller.dev = dev;
> +	ipcc->controller.txdone_irq = true;
> +	ipcc->controller.ops = &stm32_ipcc_ops;
> +	ipcc->controller.num_chans = ipcc->n_chans;
> +	ipcc->controller.chans = devm_kcalloc(dev, ipcc->controller.num_chans,
> +					      sizeof(*ipcc->controller.chans),
> +					      GFP_KERNEL);
> +	if (!ipcc->controller.chans) {
> +		ret = -ENOMEM;
> +		goto err_irq_wkp;
> +	}
> +
> +	for (i = 0; i < ipcc->controller.num_chans; i++)
> +		ipcc->controller.chans[i].con_priv = (void *)i;
> +
> +	ret = mbox_controller_register(&ipcc->controller);
> +	if (ret)
> +		goto err_irq_wkp;
> +
> +	platform_set_drvdata(pdev, ipcc);
> +
> +	ip_ver = readl_relaxed(ipcc->reg_base + IPCC_VER);
> +
> +	dev_info(dev, "ipcc rev:%ld.%ld enabled, %d chans, proc %d\n",
> +		 FIELD_GET(VER_MAJREV_MASK, ip_ver),
> +		 FIELD_GET(VER_MINREV_MASK, ip_ver),
> +		 ipcc->controller.num_chans, ipcc->proc_id);
> +
> +	clk_disable_unprepare(ipcc->clk);
> +	return 0;
> +
> +err_irq_wkp:
> +	if (ipcc->wkp)
> +		dev_pm_clear_wake_irq(dev);
> +err_init_wkp:
> +	device_init_wakeup(dev, false);
> +err_clk:
> +	clk_disable_unprepare(ipcc->clk);
> +	return ret;
> +}
> +
> +static int stm32_ipcc_remove(struct platform_device *pdev)
> +{
> +	struct stm32_ipcc *ipcc = platform_get_drvdata(pdev);
> +
> +	mbox_controller_unregister(&ipcc->controller);
> +
> +	if (ipcc->wkp)
> +		dev_pm_clear_wake_irq(&pdev->dev);
> +
> +	device_init_wakeup(&pdev->dev, false);
> +
> +	return 0;
> +}
> +
> +#ifdef CONFIG_PM_SLEEP
> +static void stm32_ipcc_set_irq_wake(struct device *dev, bool enable)
> +{
> +	struct stm32_ipcc *ipcc = dev_get_drvdata(dev);
> +	unsigned int i;
> +
> +	if (device_may_wakeup(dev))
> +		for (i = 0; i < IPCC_IRQ_NUM; i++)
> +			irq_set_irq_wake(ipcc->irqs[i], enable);
> +}
> +
> +static int stm32_ipcc_suspend(struct device *dev)
> +{
> +	struct stm32_ipcc *ipcc = dev_get_drvdata(dev);
> +
> +	ipcc->xmr = readl_relaxed(ipcc->reg_proc + IPCC_XMR);
> +	ipcc->xcr = readl_relaxed(ipcc->reg_proc + IPCC_XCR);
> +
> +	stm32_ipcc_set_irq_wake(dev, true);
> +
> +	return 0;
> +}
> +
> +static int stm32_ipcc_resume(struct device *dev)
> +{
> +	struct stm32_ipcc *ipcc = dev_get_drvdata(dev);
> +
> +	stm32_ipcc_set_irq_wake(dev, false);
> +
> +	writel_relaxed(ipcc->xmr, ipcc->reg_proc + IPCC_XMR);
> +	writel_relaxed(ipcc->xcr, ipcc->reg_proc + IPCC_XCR);
> +
> +	return 0;
> +}
> +#endif
> +
> +static SIMPLE_DEV_PM_OPS(stm32_ipcc_pm_ops,
> +			 stm32_ipcc_suspend, stm32_ipcc_resume);
> +
> +static const struct of_device_id stm32_ipcc_of_match[] = {
> +	{ .compatible = "st,stm32mp1-ipcc" },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, stm32_ipcc_of_match);
> +
> +static struct platform_driver stm32_ipcc_driver = {
> +	.driver = {
> +		.name = "stm32-ipcc",
> +		.pm = &stm32_ipcc_pm_ops,
> +		.of_match_table = stm32_ipcc_of_match,
> +	},
> +	.probe		= stm32_ipcc_probe,
> +	.remove		= stm32_ipcc_remove,
> +};
> +
> +module_platform_driver(stm32_ipcc_driver);
> +
> +MODULE_AUTHOR("Ludovic Barre <ludovic.barre@st.com>");
> +MODULE_AUTHOR("Fabien Dessenne <fabien.dessenne@st.com>");
> +MODULE_DESCRIPTION("STM32 IPCC driver");
> +MODULE_LICENSE("GPL v2");

^ permalink raw reply

* [PATCH V4 0/3] Microchip mcp25xxfd can controller driver
From: kernel @ 2018-05-14 16:03 UTC (permalink / raw)
  To: linux-can, devicetree, Wolfgang Grandegger, Mark Kleine-Budde,
	Rob Herring, Mark Rutland
  Cc: Martin Sperl, Marcel Birthelmer, Sukkin Pang, Gerhard Bertelsmann

From: Martin Sperl <kernel@martin.sperl.org>

This patchset adds a driver for the mcp25xxfd CanFD controller.

Most of the features of the controller are supported by this
driver release.

The controller includes a few features that the current core Can(FD)
implementation does not support:
* Transmit Bandwidth Sharing bits
  - this waits for a configurable number of syncronization bits before
    atempting to transmit the next frame - for the moment controllable
    via a module parameter
* SID11 with CanFD frames
  - at this moment not supported by driver
* 3 transmit attempts in addition to ONE_SHOT
* transmitter delay compensation configurations
* micro second exact transmission and reception timestamps
  - used internally by driver
* variable number of tx-fifos
  - exposed via module parameter at this moment

The driver has been heavily optimized so that it can handle
a 100% utilized 1MHz Can-bus (with 11 bit can frames with DLC=0)
even on less powerful SOCs like the raspberry pi 1 without
dropping frames due to driver/spi latencies (still dropps
are observed in the can/network stack).

To achive this the driver includes several code optimization
Two of which prefers continuous long spi transfers over multiple
small individual spi transfers of variable length.
This is an optimization that would favour spi dma transfers instead
of more expensive PIO in typical SPI controllers.
Both of them can get explicitly enabled by the use of a module parameter.
One of those is automatically used in the case of Can2.0 only bus
configurations.

Finer details of the implementation and rational is included with
the driver code it self for future reference.

The driver has also been tested for basic CanFD transfers with
BRS up to 5.7Mhz data bitrates - the available transceiver did
not allow for faster rates...

The driver itself exposes lots of internal data/statistics via
debugfs.

The driver implements a lock-less design for transmissions
making use instead of prepared spi messages submitted via spi_async
for transmission in the start_xmit_start code without requireing
an extra workqueue and the corresponding latencies.

Note that there is an extra patch in the pipeline to implement pinctrl
but this still shows some issues, so it is not included in this patchset.

Changelog:
  V1 -> V2: new more generic name based on feedback from microchip
            cleanup of code (checkpatch)
            address all feedback on code
            handle (HW) systemerrors in a much better/reliable way
            cleanup of dt custom properties removing (most) gpio 
              related properties
            implemented GPIOLIB support as per feedback
  V2 -> V3: added vendor-prefix for gpio-opendrain to dt-binding
            added gpio-controller to dt-binding
	    added feedback by Rob Herring
	    waited for other feedback regarding the code
  V3 -> V4: resend
            Patch-1 (dt) added: Reviewed-by: Rob Herring <robh@kernel.org>

Martin Sperl (3):
  dt-binding: can: mcp25xxfd: document device tree bindings
  can: mcp25xxfd: Add Microchip mcp25xxfd CAN FD driver
  can: mcp25xxfd: add gpiolib support for GPIO0/1 (aka. INT0/INT1)

 .../bindings/net/can/microchip,mcp25xxfd.txt       |   32 +
 drivers/net/can/spi/Kconfig                        |    6 +
 drivers/net/can/spi/Makefile                       |    1 +
 drivers/net/can/spi/mcp25xxfd.c                    | 4509 ++++++++++++++++++++
 4 files changed, 4548 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.txt
 create mode 100644 drivers/net/can/spi/mcp25xxfd.c

--
2.11.0

^ permalink raw reply

* [PATCH V4 1/3] dt-binding: can: mcp25xxfd: document device tree bindings
From: kernel @ 2018-05-14 16:03 UTC (permalink / raw)
  To: linux-can, devicetree, Wolfgang Grandegger, Mark Kleine-Budde,
	Rob Herring, Mark Rutland
  Cc: Martin Sperl, Marcel Birthelmer, Sukkin Pang, Gerhard Bertelsmann
In-Reply-To: <20180514160313.29597-1-kernel@martin.sperl.org>

From: Martin Sperl <kernel@martin.sperl.org>

Add device-tree bindings for Microcip CanFD Controller mcp2517fd

Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
Reviewed-by: Rob Herring <robh@kernel.org>
--
Changelog:
  V1 -> V2: new more generic name based on feedback from microchip
            cleanup of dt custom properties removing (most) gpio functions
  V2 -> V3: added vendor-prefix for gpio-opendrain
            s/_/-/
            added gpio-controller
  V3 -> V4: resend
            added: Reviewed-by: Rob Herring <robh@kernel.org>

---
 .../bindings/net/can/microchip,mcp25xxfd.txt       | 32 ++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.txt

diff --git a/Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.txt b/Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.txt
new file mode 100644
index 000000000000..b388b3eb3905
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/can/microchip,mcp25xxfd.txt
@@ -0,0 +1,32 @@
+* Microchip MCP2517 stand-alone CAN controller device tree bindings
+
+Required properties:
+ - compatible: Should be one of the following:
+   - "microchip,mcp2517fd" for MCP2517fd.
+ - reg: SPI chip select.
+ - clocks: The clock feeding the CAN controller.
+ - interrupt-parent: The parent interrupt controller.
+ - interrupts: Should contain IRQ line for the CAN controller.
+ - gpio-controller: Marks the device node as a GPIO controller
+
+Optional properties:
+ - vdd-supply: Regulator that powers the CAN controller.
+ - xceiver-supply: Regulator that powers the CAN transceiver.
+ - microchip,clock-out-div = <0|1|2|4|10>: Clock output pin divider
+					   0 = Start of Frame output
+					   default: 10
+ - microchip,clock-div2: bool: divide the internal clock by 2
+ - microchip,gpio-open-drain: bool: enable open-drain for all pins
+                                    (except cantx)
+
+Example:
+	can0: can@1 {
+		compatible = "microchip,mcp2517fd";
+		reg = <1>;
+		clocks = <&clk24m>;
+		interrupt-parent = <&gpio4>;
+		interrupts = <13 0x8>;
+		vdd-supply = <&reg5v0>;
+		xceiver-supply = <&reg5v0>;
+		gpio-controller;
+	};
--
2.11.0

^ permalink raw reply related

* [PATCH V4 2/3] can: mcp25xxfd: Add Microchip mcp25xxfd CAN FD driver
From: kernel @ 2018-05-14 16:03 UTC (permalink / raw)
  To: linux-can, devicetree, Wolfgang Grandegger, Mark Kleine-Budde,
	Rob Herring, Mark Rutland
  Cc: Martin Sperl, Marcel Birthelmer, Sukkin Pang, Gerhard Bertelsmann
In-Reply-To: <20180514160313.29597-1-kernel@martin.sperl.org>

From: Martin Sperl <kernel@martin.sperl.org>

This patch adds support for the Microchip mcp2517fd CAN-FD controller.
The mcp2517fd is capable of transmitting and receiving standard data
frames, extended data frames, remote frames and Can-FD frames.
The mcp2517fd interfaces with the host over SPI.

Datasheet:
* http://ww1.microchip.com/downloads/en/DeviceDoc/20005688A.pdf
Reference manual:
* http://ww1.microchip.com/downloads/en/DeviceDoc/20005678A.pdf

Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
Tested-by: Gerhard Bertelsmann <info@gerhard-bertelsmann.de>
---
Changelog:
  V1 -> V2: new more generic name based on feedback from microchip
            cleanup of code (checkpatch)
            address all feedback on code
            prepare code for GPIO handling (separate patches)
            handle systemerrors in a much better/reliable way
  V2 -> V3: added vendor-prefix for gpio-opendrain
  V3 -> V4: resend

---
 drivers/net/can/spi/Kconfig     |    6 +
 drivers/net/can/spi/Makefile    |    1 +
 drivers/net/can/spi/mcp25xxfd.c | 4324 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 4331 insertions(+)
 create mode 100644 drivers/net/can/spi/mcp25xxfd.c

diff --git a/drivers/net/can/spi/Kconfig b/drivers/net/can/spi/Kconfig
index 8f2e0dd7b756..06d588e28c35 100644
--- a/drivers/net/can/spi/Kconfig
+++ b/drivers/net/can/spi/Kconfig
@@ -13,4 +13,10 @@ config CAN_MCP251X
 	---help---
 	  Driver for the Microchip MCP251x SPI CAN controllers.

+config CAN_MCP25XXFD
+	tristate "Microchip MCP25xxFD SPI CAN controllers"
+	depends on HAS_DMA
+	---help---
+	  Driver for the Microchip MCP25XXFD SPI FD-CAN controller family.
+
 endmenu
diff --git a/drivers/net/can/spi/Makefile b/drivers/net/can/spi/Makefile
index f59fa3731073..805643e0a01e 100644
--- a/drivers/net/can/spi/Makefile
+++ b/drivers/net/can/spi/Makefile
@@ -5,3 +5,4 @@

 obj-$(CONFIG_CAN_HI311X)	+= hi311x.o
 obj-$(CONFIG_CAN_MCP251X)	+= mcp251x.o
+obj-$(CONFIG_CAN_MCP25XXFD)	+= mcp25xxfd.o
diff --git a/drivers/net/can/spi/mcp25xxfd.c b/drivers/net/can/spi/mcp25xxfd.c
new file mode 100644
index 000000000000..49a454da2196
--- /dev/null
+++ b/drivers/net/can/spi/mcp25xxfd.c
@@ -0,0 +1,4324 @@
+/*
+ * CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
+ *
+ * Copyright 2017 Martin Sperl <kernel@martin.sperl.org>
+ *
+ * SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
+ *
+ * Based on Microchip MCP251x CAN controller driver written by
+ * David Vrabel, Copyright 2006 Arcom Control Systems Ltd.
+ *
+ */
+
+#include <linux/can/core.h>
+#include <linux/can/dev.h>
+#include <linux/can/led.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/freezer.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/spi/spi.h>
+#include <linux/uaccess.h>
+#include <linux/regulator/consumer.h>
+
+#define DEVICE_NAME "mcp25xxfd"
+
+/* device description and rational:
+ *
+ * the mcp25xxfd is a CanFD controller that also supports can2.0 only
+ * modes.
+ * It is connected via spi to the host and requires at minimum a single
+ * irq line in addition to the SPI lines - it is not mentioned explicitly
+ * in the documentation but in principle SPI 3-wire should be possible.
+ *
+ * The clock connected is typically 4MHz, 20MHz or 40MHz.
+ * For the 4MHz clock the controller contains 10x PLL circuitry.
+ *
+ * The controller itself has 2KB or ECC-SRAM for data.
+ * It also has 32 FIFOs (of up to 32 CAN-frames).
+ * There are 4 Fifo types which can get configured:
+ * * TEF - Transmission Event Fifo - which consumes FIFO 0
+ *   even if it is not configured
+ * * Tansmission Queue - for up to 32 Frames.
+ *   this queue reorders CAN frames to get transmitted following the
+ *   typical CAN dominant/recessive rules on the can bus itself.
+ *   This FIFO is optional.
+ * * TX FIFO: generic TX fifos that can contain arbitrary data
+ *   and which come with a configurable priority for transmission
+ *   It is also possible to have the Controller automatically trigger
+ *   a transfer when a Filter Rule for a RTR frame matches.
+ *   Each of these fifos in principle can get configured for distinct
+ *   dlc sizes (8 thru 64 bytes)
+ * * RX FIFO: generic RX fifo which is filled via filter-rules.
+ *   Each of these fifos in principle can get configured for distinct
+ *   dlc sizes (8 thru 64 bytes)
+ *   Unfortunately there is no filter rule that would allow triggering
+ *   on different frame sizes, so for all practical purposes the
+ *   RX fifos have to be of the same size (unless one wants to experience
+ *   lost data).
+ * When a Can Frame is transmitted fromthe TX Queue or an individual
+ * TX FIFO then a small TEF Frame can get added to the TEF FIFO queue
+ * to log the Transmission of the frame - this includes ID, Flags
+ * (including a custom identifier/index) .
+ *
+ * The controller provides an optional free running counter with a divider
+ * for timestamping of RX frames as well as for TEF entries.
+ *
+ * Driver Implementation details and rational:
+ * * The whole driver has been designed to give best performance
+ *   and as little packet loss as possible with 1MHZ Can frames with DLC=0
+ *   on small/slow devices like the Raspberry Pi 1
+ * * This means that some optimizations for full duplex communication
+ *   have been implemented to avoid CPU introduced latencies
+ *   (especially for spi_write_then_read cases) - this only applies to
+ *   4 wire SPI busses.
+ * * Due to the fact that the TXQ does reorder Can-Frames we do not make
+ *   use of it to avoid unexpected behaviour (say when running a
+ *   firmware upgrade via Can)
+ * * this means we use individual TX-fifos with a given priority and
+ *   we have to wait until all the TX fifos have been transmitted before
+ *   we can restart the networking queue to avoid reordering the frames on
+ *   the Can bus itself.
+ *   Still we can transmit a transmit only Duty-cycle of 66% to 90% on the
+ *   Can bus (at 1MHz).
+ *   The scaling factors here are:
+ *   * Can bus speed - lower Speeds increase Duty-cycle
+ *   * SPI Clock Rate - higher speeds increase duty-cycle
+ *   * CPU speed + SPI implementation - reduces latencies between transfers
+ * * There is a module parameter that allows the modification of the
+ *   number of tx_fifos, which is by default 7.
+ * * The driver offers some module parameters that allow to control the use
+ *   of some optimizations (prefer reading more data than necessary instead
+ *   of multiple SPI transfers - the idea here is that this way we may
+ *   allow the SPI-controller to use DMA instead of programmed IO to
+ *   limit latencies/number of interrupts)
+ *   When we have to read multiple RX frames in CanFD mode:
+ *   * we allow reading all 64 bytes of payload even if DLC <=8
+ *     this mode is used in Can2.0 only mode by default and can not get
+ *     disabled (SRAM Reads have to be a multiple of 4 bytes anyway)
+ *   * Releasing/freeing the RX queue requires writing of 1 byte per fifo.
+ *     unfortunately these 32-bit registers are not ajacent to each other,
+ *     so that for 2 consecutive RX Frames instead of writing 1 byte per
+ *     fifo (with protocol overhead of 2 bytes - so a total of 6 bytes in
+ *     2 transfers) we transmit 13 bytes (with a protocol overhead of 2 -
+ *     so a total of 15 bytes)
+ *     This optimization is only enabled by a module parameter.
+ * * we use TEF + time stamping to record the transmitted frames
+ *   including their timestamp - we use this to order TX and RX frames
+ *   when submitting them to the network stack.
+ * * due to the inability to "filter" based on DLC sizes we have to use
+ *   a common FIFO size. This is 8 bytes for Can2.0 and 64 bytes for CanFD.
+ * * the driver tries to detect the Controller only by reading registers,
+ *   but there are circumstances (e.g. after a crashed driver) where we
+ *   have to "blindly" configure the clock rate to get the controller to
+ *   respond correctly.
+ * * There is one situation where the controller will require a full POR
+ *   (total power off) to recover from a bad Clock configuration.
+ *   This happens when the wrong clock is configured in the device tree
+ *   (say 4MHz are configured, while 20 or 40MHz are used)
+ *   in such a situation the driver tries to enable the PLL, which will
+ *   never synchronize and the controller becomes unresponsive to further
+ *   spi requests until a POR.
+ */
+
+#define MCP25XXFD_OST_DELAY_MS		3
+#define MCP25XXFD_MIN_CLOCK_FREQUENCY	1000000
+#define MCP25XXFD_MAX_CLOCK_FREQUENCY	40000000
+#define MCP25XXFD_PLL_MULTIPLIER	10
+#define MCP25XXFD_AUTO_PLL_MAX_CLOCK_FREQUENCY				\
+	(MCP25XXFD_MAX_CLOCK_FREQUENCY / MCP25XXFD_PLL_MULTIPLIER)
+#define MCP25XXFD_SCLK_DIVIDER		2
+
+#define MCP25XXFD_OSC_POLLING_JIFFIES	(HZ / 2)
+
+#define TX_ECHO_SKB_MAX	32
+
+#define INSTRUCTION_RESET		0x0000
+#define INSTRUCTION_READ		0x3000
+#define INSTRUCTION_WRITE		0x2000
+#define INSTRUCTION_READ_CRC		0xB000
+#define INSTRUCTION_WRITE_CRC		0xA000
+#define INSTRUCTION_WRITE_SAVE		0xC000
+
+#define ADDRESS_MASK			0x0fff
+
+#define MCP25XXFD_SFR_BASE(x)		(0xE00 + (x))
+#define MCP25XXFD_OSC			MCP25XXFD_SFR_BASE(0x00)
+#  define MCP25XXFD_OSC_PLLEN		BIT(0)
+#  define MCP25XXFD_OSC_OSCDIS		BIT(2)
+#  define MCP25XXFD_OSC_SCLKDIV		BIT(4)
+#  define MCP25XXFD_OSC_CLKODIV_BITS	2
+#  define MCP25XXFD_OSC_CLKODIV_SHIFT	5
+#  define MCP25XXFD_OSC_CLKODIV_MASK			\
+	GENMASK(MCP25XXFD_OSC_CLKODIV_SHIFT		\
+		+ MCP25XXFD_OSC_CLKODIV_BITS - 1,	\
+		MCP25XXFD_OSC_CLKODIV_SHIFT)
+#  define MCP25XXFD_OSC_CLKODIV_10	3
+#  define MCP25XXFD_OSC_CLKODIV_4	2
+#  define MCP25XXFD_OSC_CLKODIV_2	1
+#  define MCP25XXFD_OSC_CLKODIV_1	0
+#  define MCP25XXFD_OSC_PLLRDY		BIT(8)
+#  define MCP25XXFD_OSC_OSCRDY		BIT(10)
+#  define MCP25XXFD_OSC_SCLKRDY		BIT(12)
+#define MCP25XXFD_IOCON			MCP25XXFD_SFR_BASE(0x04)
+#  define MCP25XXFD_IOCON_TRIS0		BIT(0)
+#  define MCP25XXFD_IOCON_TRIS1		BIT(1)
+#  define MCP25XXFD_IOCON_XSTBYEN	BIT(6)
+#  define MCP25XXFD_IOCON_LAT0		BIT(8)
+#  define MCP25XXFD_IOCON_LAT1		BIT(9)
+#  define MCP25XXFD_IOCON_GPIO0		BIT(16)
+#  define MCP25XXFD_IOCON_GPIO1		BIT(17)
+#  define MCP25XXFD_IOCON_PM0		BIT(24)
+#  define MCP25XXFD_IOCON_PM1		BIT(25)
+#  define MCP25XXFD_IOCON_TXCANOD	BIT(28)
+#  define MCP25XXFD_IOCON_SOF		BIT(29)
+#  define MCP25XXFD_IOCON_INTOD		BIT(29)
+#define MCP25XXFD_CRC			MCP25XXFD_SFR_BASE(0x08)
+#  define MCP25XXFD_CRC_MASK		GENMASK(15, 0)
+#  define MCP25XXFD_CRC_CRCERRIE	BIT(16)
+#  define MCP25XXFD_CRC_FERRIE		BIT(17)
+#  define MCP25XXFD_CRC_CRCERRIF	BIT(24)
+#  define MCP25XXFD_CRC_FERRIF		BIT(25)
+#define MCP25XXFD_ECCCON		MCP25XXFD_SFR_BASE(0x0C)
+#  define MCP25XXFD_ECCCON_ECCEN	BIT(0)
+#  define MCP25XXFD_ECCCON_SECIE	BIT(1)
+#  define MCP25XXFD_ECCCON_DEDIE	BIT(2)
+#  define MCP25XXFD_ECCCON_PARITY_BITS 6
+#  define MCP25XXFD_ECCCON_PARITY_SHIFT 8
+#  define MCP25XXFD_ECCCON_PARITY_MASK			\
+	GENMASK(MCP25XXFD_ECCCON_PARITY_SHIFT		\
+		+ MCP25XXFD_ECCCON_PARITY_BITS - 1,	\
+		MCP25XXFD_ECCCON_PARITY_SHIFT)
+#define MCP25XXFD_ECCSTAT		MCP25XXFD_SFR_BASE(0x10)
+#  define MCP25XXFD_ECCSTAT_SECIF	BIT(1)
+#  define MCP25XXFD_ECCSTAT_DEDIF	BIT(2)
+#  define MCP25XXFD_ECCSTAT_ERRADDR_SHIFT 8
+#  define MCP25XXFD_ECCSTAT_ERRADDR_MASK	      \
+	GENMASK(MCP25XXFD_ECCSTAT_ERRADDR_SHIFT + 11, \
+		MCP25XXFD_ECCSTAT_ERRADDR_SHIFT)
+
+#define CAN_SFR_BASE(x)			(0x000 + (x))
+#define CAN_CON				CAN_SFR_BASE(0x00)
+#  define CAN_CON_DNCNT_BITS		5
+#  define CAN_CON_DNCNT_SHIFT		0
+#  define CAN_CON_DNCNT_MASK					\
+	GENMASK(CAN_CON_DNCNT_SHIFT + CAN_CON_DNCNT_BITS - 1,	\
+		CAN_CON_DNCNT_SHIFT)
+#  define CAN_CON_ISOCRCEN		BIT(5)
+#  define CAN_CON_PXEDIS		BIT(6)
+#  define CAN_CON_WAKFIL		BIT(8)
+#  define CAN_CON_WFT_BITS		2
+#  define CAN_CON_WFT_SHIFT		9
+#  define CAN_CON_WFT_MASK					\
+	GENMASK(CAN_CON_WFT_SHIFT + CAN_CON_WFT_BITS - 1,	\
+		CAN_CON_WFT_SHIFT)
+#  define CAN_CON_BUSY			BIT(11)
+#  define CAN_CON_BRSDIS		BIT(12)
+#  define CAN_CON_RTXAT			BIT(16)
+#  define CAN_CON_ESIGM			BIT(17)
+#  define CAN_CON_SERR2LOM		BIT(18)
+#  define CAN_CON_STEF			BIT(19)
+#  define CAN_CON_TXQEN			BIT(20)
+#  define CAN_CON_OPMODE_BITS		3
+#  define CAN_CON_OPMOD_SHIFT		21
+#  define CAN_CON_OPMOD_MASK					\
+	GENMASK(CAN_CON_OPMOD_SHIFT + CAN_CON_OPMODE_BITS - 1,	\
+		CAN_CON_OPMOD_SHIFT)
+#  define CAN_CON_REQOP_BITS		3
+#  define CAN_CON_REQOP_SHIFT		24
+#  define CAN_CON_REQOP_MASK					\
+	GENMASK(CAN_CON_REQOP_SHIFT + CAN_CON_REQOP_BITS - 1,	\
+		CAN_CON_REQOP_SHIFT)
+#    define CAN_CON_MODE_MIXED			0
+#    define CAN_CON_MODE_SLEEP			1
+#    define CAN_CON_MODE_INTERNAL_LOOPBACK	2
+#    define CAN_CON_MODE_LISTENONLY		3
+#    define CAN_CON_MODE_CONFIG			4
+#    define CAN_CON_MODE_EXTERNAL_LOOPBACK	5
+#    define CAN_CON_MODE_CAN2_0			6
+#    define CAN_CON_MODE_RESTRICTED		7
+#  define CAN_CON_ABAT			BIT(27)
+#  define CAN_CON_TXBWS_BITS		3
+#  define CAN_CON_TXBWS_SHIFT		28
+#  define CAN_CON_TXBWS_MASK					\
+	GENMASK(CAN_CON_TXBWS_SHIFT + CAN_CON_TXBWS_BITS - 1,	\
+		CAN_CON_TXBWS_SHIFT)
+#  define CAN_CON_DEFAULT				\
+	(CAN_CON_ISOCRCEN |				\
+	 CAN_CON_PXEDIS |				\
+	 CAN_CON_WAKFIL |				\
+	 (3 << CAN_CON_WFT_SHIFT) |			\
+	 CAN_CON_STEF |					\
+	 CAN_CON_TXQEN |				\
+	 (CAN_CON_MODE_CONFIG << CAN_CON_OPMOD_SHIFT) |	\
+	 (CAN_CON_MODE_CONFIG << CAN_CON_REQOP_SHIFT))
+#  define CAN_CON_DEFAULT_MASK	\
+	(CAN_CON_DNCNT_MASK |	\
+	 CAN_CON_ISOCRCEN |	\
+	 CAN_CON_PXEDIS |	\
+	 CAN_CON_WAKFIL |	\
+	 CAN_CON_WFT_MASK |	\
+	 CAN_CON_BRSDIS |	\
+	 CAN_CON_RTXAT |	\
+	 CAN_CON_ESIGM |	\
+	 CAN_CON_SERR2LOM |	\
+	 CAN_CON_STEF |		\
+	 CAN_CON_TXQEN |	\
+	 CAN_CON_OPMOD_MASK |	\
+	 CAN_CON_REQOP_MASK |	\
+	 CAN_CON_ABAT |		\
+	 CAN_CON_TXBWS_MASK)
+#define CAN_NBTCFG			CAN_SFR_BASE(0x04)
+#  define CAN_NBTCFG_SJW_BITS		7
+#  define CAN_NBTCFG_SJW_SHIFT		0
+#  define CAN_NBTCFG_SJW_MASK					\
+	GENMASK(CAN_NBTCFG_SJW_SHIFT + CAN_NBTCFG_SJW_BITS - 1, \
+		CAN_NBTCFG_SJW_SHIFT)
+#  define CAN_NBTCFG_TSEG2_BITS		7
+#  define CAN_NBTCFG_TSEG2_SHIFT	8
+#  define CAN_NBTCFG_TSEG2_MASK					    \
+	GENMASK(CAN_NBTCFG_TSEG2_SHIFT + CAN_NBTCFG_TSEG2_BITS - 1, \
+		CAN_NBTCFG_TSEG2_SHIFT)
+#  define CAN_NBTCFG_TSEG1_BITS		8
+#  define CAN_NBTCFG_TSEG1_SHIFT	16
+#  define CAN_NBTCFG_TSEG1_MASK					    \
+	GENMASK(CAN_NBTCFG_TSEG1_SHIFT + CAN_NBTCFG_TSEG1_BITS - 1, \
+		CAN_NBTCFG_TSEG1_SHIFT)
+#  define CAN_NBTCFG_BRP_BITS		8
+#  define CAN_NBTCFG_BRP_SHIFT		24
+#  define CAN_NBTCFG_BRP_MASK					\
+	GENMASK(CAN_NBTCFG_BRP_SHIFT + CAN_NBTCFG_BRP_BITS - 1, \
+		CAN_NBTCFG_BRP_SHIFT)
+#define CAN_DBTCFG			CAN_SFR_BASE(0x08)
+#  define CAN_DBTCFG_SJW_BITS		4
+#  define CAN_DBTCFG_SJW_SHIFT		0
+#  define CAN_DBTCFG_SJW_MASK					\
+	GENMASK(CAN_DBTCFG_SJW_SHIFT + CAN_DBTCFG_SJW_BITS - 1, \
+		CAN_DBTCFG_SJW_SHIFT)
+#  define CAN_DBTCFG_TSEG2_BITS		4
+#  define CAN_DBTCFG_TSEG2_SHIFT	8
+#  define CAN_DBTCFG_TSEG2_MASK					    \
+	GENMASK(CAN_DBTCFG_TSEG2_SHIFT + CAN_DBTCFG_TSEG2_BITS - 1, \
+		CAN_DBTCFG_TSEG2_SHIFT)
+#  define CAN_DBTCFG_TSEG1_BITS		5
+#  define CAN_DBTCFG_TSEG1_SHIFT	16
+#  define CAN_DBTCFG_TSEG1_MASK					    \
+	GENMASK(CAN_DBTCFG_TSEG1_SHIFT + CAN_DBTCFG_TSEG1_BITS - 1, \
+		CAN_DBTCFG_TSEG1_SHIFT)
+#  define CAN_DBTCFG_BRP_BITS		8
+#  define CAN_DBTCFG_BRP_SHIFT		24
+#  define CAN_DBTCFG_BRP_MASK					\
+	GENMASK(CAN_DBTCFG_BRP_SHIFT + CAN_DBTCFG_BRP_BITS - 1, \
+		CAN_DBTCFG_BRP_SHIFT)
+#define CAN_TDC				CAN_SFR_BASE(0x0C)
+#  define CAN_TDC_TDCV_BITS		5
+#  define CAN_TDC_TDCV_SHIFT		0
+#  define CAN_TDC_TDCV_MASK					\
+	GENMASK(CAN_TDC_TDCV_SHIFT + CAN_TDC_TDCV_BITS - 1, \
+		CAN_TDC_TDCV_SHIFT)
+#  define CAN_TDC_TDCO_BITS		5
+#  define CAN_TDC_TDCO_SHIFT		8
+#  define CAN_TDC_TDCO_MASK					\
+	GENMASK(CAN_TDC_TDCO_SHIFT + CAN_TDC_TDCO_BITS - 1, \
+		CAN_TDC_TDCO_SHIFT)
+#  define CAN_TDC_TDCMOD_BITS		2
+#  define CAN_TDC_TDCMOD_SHIFT		16
+#  define CAN_TDC_TDCMOD_MASK					\
+	GENMASK(CAN_TDC_TDCMOD_SHIFT + CAN_TDC_TDCMOD_BITS - 1, \
+		CAN_TDC_TDCMOD_SHIFT)
+#  define CAN_TDC_TDCMOD_DISABLED	0
+#  define CAN_TDC_TDCMOD_MANUAL		1
+#  define CAN_TDC_TDCMOD_AUTO		2
+#  define CAN_TDC_SID11EN		BIT(24)
+#  define CAN_TDC_EDGFLTEN		BIT(25)
+#define CAN_TBC				CAN_SFR_BASE(0x10)
+#define CAN_TSCON			CAN_SFR_BASE(0x14)
+#  define CAN_TSCON_TBCPRE_BITS		10
+#  define CAN_TSCON_TBCPRE_SHIFT	0
+#  define CAN_TSCON_TBCPRE_MASK					    \
+	GENMASK(CAN_TSCON_TBCPRE_SHIFT + CAN_TSCON_TBCPRE_BITS - 1, \
+		CAN_TSCON_TBCPRE_SHIFT)
+#  define CAN_TSCON_TBCEN		BIT(16)
+#  define CAN_TSCON_TSEOF		BIT(17)
+#  define CAN_TSCON_TSRES		BIT(18)
+#define CAN_VEC				CAN_SFR_BASE(0x18)
+#  define CAN_VEC_ICODE_BITS		7
+#  define CAN_VEC_ICODE_SHIFT		0
+#  define CAN_VEC_ICODE_MASK					    \
+	GENMASK(CAN_VEC_ICODE_SHIFT + CAN_VEC_ICODE_BITS - 1,	    \
+		CAN_VEC_ICODE_SHIFT)
+#  define CAN_VEC_FILHIT_BITS		5
+#  define CAN_VEC_FILHIT_SHIFT		8
+#  define CAN_VEC_FILHIT_MASK					\
+	GENMASK(CAN_VEC_FILHIT_SHIFT + CAN_VEC_FILHIT_BITS - 1, \
+		CAN_VEC_FILHIT_SHIFT)
+#  define CAN_VEC_TXCODE_BITS		7
+#  define CAN_VEC_TXCODE_SHIFT		16
+#  define CAN_VEC_TXCODE_MASK					\
+	GENMASK(CAN_VEC_TXCODE_SHIFT + CAN_VEC_TXCODE_BITS - 1, \
+		CAN_VEC_TXCODE_SHIFT)
+#  define CAN_VEC_RXCODE_BITS		7
+#  define CAN_VEC_RXCODE_SHIFT		24
+#  define CAN_VEC_RXCODE_MASK					\
+	GENMASK(CAN_VEC_RXCODE_SHIFT + CAN_VEC_RXCODE_BITS - 1, \
+		CAN_VEC_RXCODE_SHIFT)
+#define CAN_INT				CAN_SFR_BASE(0x1C)
+#  define CAN_INT_IF_SHIFT		0
+#  define CAN_INT_TXIF			BIT(0)
+#  define CAN_INT_RXIF			BIT(1)
+#  define CAN_INT_TBCIF			BIT(2)
+#  define CAN_INT_MODIF			BIT(3)
+#  define CAN_INT_TEFIF			BIT(4)
+#  define CAN_INT_ECCIF			BIT(8)
+#  define CAN_INT_SPICRCIF		BIT(9)
+#  define CAN_INT_TXATIF		BIT(10)
+#  define CAN_INT_RXOVIF		BIT(11)
+#  define CAN_INT_SERRIF		BIT(12)
+#  define CAN_INT_CERRIF		BIT(13)
+#  define CAN_INT_WAKIF			BIT(14)
+#  define CAN_INT_IVMIF			BIT(15)
+#  define CAN_INT_IF_MASK		\
+	(CAN_INT_TXIF |			\
+	 CAN_INT_RXIF |			\
+	 CAN_INT_TBCIF	|		\
+	 CAN_INT_MODIF	|		\
+	 CAN_INT_TEFIF	|		\
+	 CAN_INT_ECCIF	|		\
+	 CAN_INT_SPICRCIF |		\
+	 CAN_INT_TXATIF |		\
+	 CAN_INT_RXOVIF |		\
+	 CAN_INT_CERRIF |		\
+	 CAN_INT_SERRIF |		\
+	 CAN_INT_WAKEIF |		\
+	 CAN_INT_IVMIF)
+#  define CAN_INT_IE_SHIFT		16
+#  define CAN_INT_TXIE			(CAN_INT_TXIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_RXIE			(CAN_INT_RXIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_TBCIE			(CAN_INT_TBCIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_MODIE			(CAN_INT_MODIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_TEFIE			(CAN_INT_TEFIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_ECCIE			(CAN_INT_ECCIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_SPICRCIE		\
+	(CAN_INT_SPICRCIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_TXATIE		(CAN_INT_TXATIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_RXOVIE		(CAN_INT_RXOVIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_CERRIE		(CAN_INT_CERRIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_SERRIE		(CAN_INT_SERRIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_WAKIE			(CAN_INT_WAKIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_IVMIE			(CAN_INT_IVMIF << CAN_INT_IE_SHIFT)
+#  define CAN_INT_IE_MASK		\
+	(CAN_INT_TXIE |			\
+	 CAN_INT_RXIE |			\
+	 CAN_INT_TBCIE	|		\
+	 CAN_INT_MODIE	|		\
+	 CAN_INT_TEFIE	|		\
+	 CAN_INT_ECCIE	|		\
+	 CAN_INT_SPICRCIE |		\
+	 CAN_INT_TXATIE |		\
+	 CAN_INT_RXOVIE |		\
+	 CAN_INT_CERRIE |		\
+	 CAN_INT_SERRIE |		\
+	 CAN_INT_WAKEIE |		\
+	 CAN_INT_IVMIE)
+#define CAN_RXIF			CAN_SFR_BASE(0x20)
+#define CAN_TXIF			CAN_SFR_BASE(0x24)
+#define CAN_RXOVIF			CAN_SFR_BASE(0x28)
+#define CAN_TXATIF			CAN_SFR_BASE(0x2C)
+#define CAN_TXREQ			CAN_SFR_BASE(0x30)
+#define CAN_TREC			CAN_SFR_BASE(0x34)
+#  define CAN_TREC_REC_BITS		8
+#  define CAN_TREC_REC_SHIFT		0
+#  define CAN_TREC_REC_MASK				    \
+	GENMASK(CAN_TREC_REC_SHIFT + CAN_TREC_REC_BITS - 1, \
+		CAN_TREC_REC_SHIFT)
+#  define CAN_TREC_TEC_BITS		8
+#  define CAN_TREC_TEC_SHIFT		8
+#  define CAN_TREC_TEC_MASK				    \
+	GENMASK(CAN_TREC_TEC_SHIFT + CAN_TREC_TEC_BITS - 1, \
+		CAN_TREC_TEC_SHIFT)
+#  define CAN_TREC_EWARN		BIT(16)
+#  define CAN_TREC_RXWARN		BIT(17)
+#  define CAN_TREC_TXWARN		BIT(18)
+#  define CAN_TREC_RXBP			BIT(19)
+#  define CAN_TREC_TXBP			BIT(20)
+#  define CAN_TREC_TXBO			BIT(21)
+#define CAN_BDIAG0			CAN_SFR_BASE(0x38)
+#  define CAN_BDIAG0_NRERRCNT_BITS	8
+#  define CAN_BDIAG0_NRERRCNT_SHIFT	0
+#  define CAN_BDIAG0_NRERRCNT_MASK				\
+	GENMASK(CAN_BDIAG0_NRERRCNT_SHIFT + CAN_BDIAG0_NRERRCNT_BITS - 1, \
+		CAN_BDIAG0_NRERRCNT_SHIFT)
+#  define CAN_BDIAG0_NTERRCNT_BITS	8
+#  define CAN_BDIAG0_NTERRCNT_SHIFT	8
+#  define CAN_BDIAG0_NTERRCNT_MASK					\
+	GENMASK(CAN_BDIAG0_NTERRCNT_SHIFT + CAN_BDIAG0_NTERRCNT_BITS - 1, \
+		CAN_BDIAG0_NTERRCNT_SHIFT)
+#  define CAN_BDIAG0_DRERRCNT_BITS	8
+#  define CAN_BDIAG0_DRERRCNT_SHIFT	16
+#  define CAN_BDIAG0_DRERRCNT_MASK					\
+	GENMASK(CAN_BDIAG0_DRERRCNT_SHIFT + CAN_BDIAG0_DRERRCNT_BITS - 1, \
+		CAN_BDIAG0_DRERRCNT_SHIFT)
+#  define CAN_BDIAG0_DTERRCNT_BITS	8
+#  define CAN_BDIAG0_DTERRCNT_SHIFT	24
+#  define CAN_BDIAG0_DTERRCNT_MASK					\
+	GENMASK(CAN_BDIAG0_DTERRCNT_SHIFT + CAN_BDIAG0_DTERRCNT_BITS - 1, \
+		CAN_BDIAG0_DTERRCNT_SHIFT)
+#define CAN_BDIAG1			CAN_SFR_BASE(0x3C)
+#  define CAN_BDIAG1_EFMSGCNT_BITS	16
+#  define CAN_BDIAG1_EFMSGCNT_SHIFT	0
+#  define CAN_BDIAG1_EFMSGCNT_MASK					\
+	GENMASK(CAN_BDIAG1_EFMSGCNT_SHIFT + CAN_BDIAG1_EFMSGCNT_BITS - 1, \
+		CAN_BDIAG1_EFMSGCNT_SHIFT)
+#  define CAN_BDIAG1_NBIT0ERR		BIT(16)
+#  define CAN_BDIAG1_NBIT1ERR		BIT(17)
+#  define CAN_BDIAG1_NACKERR		BIT(18)
+#  define CAN_BDIAG1_NSTUFERR		BIT(19)
+#  define CAN_BDIAG1_NFORMERR		BIT(20)
+#  define CAN_BDIAG1_NCRCERR		BIT(21)
+#  define CAN_BDIAG1_TXBOERR		BIT(23)
+#  define CAN_BDIAG1_DBIT0ERR		BIT(24)
+#  define CAN_BDIAG1_DBIT1ERR		BIT(25)
+#  define CAN_BDIAG1_DFORMERR		BIT(27)
+#  define CAN_BDIAG1_DSTUFERR		BIT(28)
+#  define CAN_BDIAG1_DCRCERR		BIT(29)
+#  define CAN_BDIAG1_ESI		BIT(30)
+#  define CAN_BDIAG1_DLCMM		BIT(31)
+#define CAN_TEFCON			CAN_SFR_BASE(0x40)
+#  define CAN_TEFCON_TEFNEIE		BIT(0)
+#  define CAN_TEFCON_TEFHIE		BIT(1)
+#  define CAN_TEFCON_TEFFIE		BIT(2)
+#  define CAN_TEFCON_TEFOVIE		BIT(3)
+#  define CAN_TEFCON_TEFTSEN		BIT(5)
+#  define CAN_TEFCON_UINC		BIT(8)
+#  define CAN_TEFCON_FRESET		BIT(10)
+#  define CAN_TEFCON_FSIZE_BITS		5
+#  define CAN_TEFCON_FSIZE_SHIFT	24
+#  define CAN_TEFCON_FSIZE_MASK					    \
+	GENMASK(CAN_TEFCON_FSIZE_SHIFT + CAN_TEFCON_FSIZE_BITS - 1, \
+		CAN_TEFCON_FSIZE_SHIFT)
+#define CAN_TEFSTA			CAN_SFR_BASE(0x44)
+#  define CAN_TEFSTA_TEFNEIF		BIT(0)
+#  define CAN_TEFSTA_TEFHIF		BIT(1)
+#  define CAN_TEFSTA_TEFFIF		BIT(2)
+#  define CAN_TEFSTA_TEVOVIF		BIT(3)
+#define CAN_TEFUA			CAN_SFR_BASE(0x48)
+#define CAN_RESERVED			CAN_SFR_BASE(0x4C)
+#define CAN_TXQCON			CAN_SFR_BASE(0x50)
+#  define CAN_TXQCON_TXQNIE		BIT(0)
+#  define CAN_TXQCON_TXQEIE		BIT(2)
+#  define CAN_TXQCON_TXATIE		BIT(4)
+#  define CAN_TXQCON_TXEN		BIT(7)
+#  define CAN_TXQCON_UINC		BIT(8)
+#  define CAN_TXQCON_TXREQ		BIT(9)
+#  define CAN_TXQCON_FRESET		BIT(10)
+#  define CAN_TXQCON_TXPRI_BITS		5
+#  define CAN_TXQCON_TXPRI_SHIFT	16
+#  define CAN_TXQCON_TXPRI_MASK					    \
+	GENMASK(CAN_TXQCON_TXPRI_SHIFT + CAN_TXQCON_TXPRI_BITS - 1, \
+		CAN_TXQCON_TXPRI_SHIFT)
+#  define CAN_TXQCON_TXAT_BITS		2
+#  define CAN_TXQCON_TXAT_SHIFT		21
+#  define CAN_TXQCON_TXAT_MASK					    \
+	GENMASK(CAN_TXQCON_TXAT_SHIFT + CAN_TXQCON_TXAT_BITS - 1, \
+		CAN_TXQCON_TXAT_SHIFT)
+#  define CAN_TXQCON_FSIZE_BITS		5
+#  define CAN_TXQCON_FSIZE_SHIFT	24
+#  define CAN_TXQCON_FSIZE_MASK					    \
+	GENMASK(CAN_TXQCON_FSIZE_SHIFT + CAN_TXQCON_FSIZE_BITS - 1, \
+		CAN_TXQCON_FSIZE_SHIFT)
+#  define CAN_TXQCON_PLSIZE_BITS	3
+#  define CAN_TXQCON_PLSIZE_SHIFT	29
+#  define CAN_TXQCON_PLSIZE_MASK				      \
+	GENMASK(CAN_TXQCON_PLSIZE_SHIFT + CAN_TXQCON_PLSIZE_BITS - 1, \
+		CAN_TXQCON_PLSIZE_SHIFT)
+#    define CAN_TXQCON_PLSIZE_8		0
+#    define CAN_TXQCON_PLSIZE_12	1
+#    define CAN_TXQCON_PLSIZE_16	2
+#    define CAN_TXQCON_PLSIZE_20	3
+#    define CAN_TXQCON_PLSIZE_24	4
+#    define CAN_TXQCON_PLSIZE_32	5
+#    define CAN_TXQCON_PLSIZE_48	6
+#    define CAN_TXQCON_PLSIZE_64	7
+
+#define CAN_TXQSTA			CAN_SFR_BASE(0x54)
+#  define CAN_TXQSTA_TXQNIF		BIT(0)
+#  define CAN_TXQSTA_TXQEIF		BIT(2)
+#  define CAN_TXQSTA_TXATIF		BIT(4)
+#  define CAN_TXQSTA_TXERR		BIT(5)
+#  define CAN_TXQSTA_TXLARB		BIT(6)
+#  define CAN_TXQSTA_TXABT		BIT(7)
+#  define CAN_TXQSTA_TXQCI_BITS		5
+#  define CAN_TXQSTA_TXQCI_SHIFT	8
+#  define CAN_TXQSTA_TXQCI_MASK					    \
+	GENMASK(CAN_TXQSTA_TXQCI_SHIFT + CAN_TXQSTA_TXQCI_BITS - 1, \
+		CAN_TXQSTA_TXQCI_SHIFT)
+
+#define CAN_TXQUA			CAN_SFR_BASE(0x58)
+#define CAN_FIFOCON(x)			CAN_SFR_BASE(0x5C + 12 * (x - 1))
+#define CAN_FIFOCON_TFNRFNIE		BIT(0)
+#define CAN_FIFOCON_TFHRFHIE		BIT(1)
+#define CAN_FIFOCON_TFERFFIE		BIT(2)
+#define CAN_FIFOCON_RXOVIE		BIT(3)
+#define CAN_FIFOCON_TXATIE		BIT(4)
+#define CAN_FIFOCON_RXTSEN		BIT(5)
+#define CAN_FIFOCON_RTREN		BIT(6)
+#define CAN_FIFOCON_TXEN		BIT(7)
+#define CAN_FIFOCON_UINC		BIT(8)
+#define CAN_FIFOCON_TXREQ		BIT(9)
+#define CAN_FIFOCON_FRESET		BIT(10)
+#  define CAN_FIFOCON_TXPRI_BITS	5
+#  define CAN_FIFOCON_TXPRI_SHIFT	16
+#  define CAN_FIFOCON_TXPRI_MASK					\
+	GENMASK(CAN_FIFOCON_TXPRI_SHIFT + CAN_FIFOCON_TXPRI_BITS - 1,	\
+		CAN_FIFOCON_TXPRI_SHIFT)
+#  define CAN_FIFOCON_TXAT_BITS		2
+#  define CAN_FIFOCON_TXAT_SHIFT	21
+#  define CAN_FIFOCON_TXAT_MASK					    \
+	GENMASK(CAN_FIFOCON_TXAT_SHIFT + CAN_FIFOCON_TXAT_BITS - 1, \
+		CAN_FIFOCON_TXAT_SHIFT)
+#  define CAN_FIFOCON_TXAT_ONE_SHOT	0
+#  define CAN_FIFOCON_TXAT_THREE_SHOT	1
+#  define CAN_FIFOCON_TXAT_UNLIMITED	2
+#  define CAN_FIFOCON_FSIZE_BITS	5
+#  define CAN_FIFOCON_FSIZE_SHIFT	24
+#  define CAN_FIFOCON_FSIZE_MASK					\
+	GENMASK(CAN_FIFOCON_FSIZE_SHIFT + CAN_FIFOCON_FSIZE_BITS - 1,	\
+		CAN_FIFOCON_FSIZE_SHIFT)
+#  define CAN_FIFOCON_PLSIZE_BITS	3
+#  define CAN_FIFOCON_PLSIZE_SHIFT	29
+#  define CAN_FIFOCON_PLSIZE_MASK					\
+	GENMASK(CAN_FIFOCON_PLSIZE_SHIFT + CAN_FIFOCON_PLSIZE_BITS - 1, \
+		CAN_FIFOCON_PLSIZE_SHIFT)
+#define CAN_FIFOSTA(x)			CAN_SFR_BASE(0x60 + 12 * (x - 1))
+#  define CAN_FIFOSTA_TFNRFNIF		BIT(0)
+#  define CAN_FIFOSTA_TFHRFHIF		BIT(1)
+#  define CAN_FIFOSTA_TFERFFIF		BIT(2)
+#  define CAN_FIFOSTA_RXOVIF		BIT(3)
+#  define CAN_FIFOSTA_TXATIF		BIT(4)
+#  define CAN_FIFOSTA_TXERR		BIT(5)
+#  define CAN_FIFOSTA_TXLARB		BIT(6)
+#  define CAN_FIFOSTA_TXABT		BIT(7)
+#  define CAN_FIFOSTA_FIFOCI_BITS	5
+#  define CAN_FIFOSTA_FIFOCI_SHIFT	8
+#  define CAN_FIFOSTA_FIFOCI_MASK					\
+	GENMASK(CAN_FIFOSTA_FIFOCI_SHIFT + CAN_FIFOSTA_FIFOCI_BITS - 1, \
+		CAN_FIFOSTA_FIFOCI_SHIFT)
+#define CAN_FIFOUA(x)			CAN_SFR_BASE(0x64 + 12 * (x - 1))
+#define CAN_FLTCON(x)			CAN_SFR_BASE(0x1D0 + (x & 0x1c))
+#  define CAN_FILCON_SHIFT(x)		((x & 3) * 8)
+#  define CAN_FILCON_BITS(x)		CAN_FILCON_BITS_
+#  define CAN_FILCON_BITS_		4
+	/* avoid macro reuse warning, so do not use GENMASK as above */
+#  define CAN_FILCON_MASK(x)					\
+	(GENMASK(CAN_FILCON_BITS_ - 1, 0) << CAN_FILCON_SHIFT(x))
+#  define CAN_FIFOCON_FLTEN(x)		BIT(7 + CAN_FILCON_SHIFT(x))
+#define CAN_FLTOBJ(x)			CAN_SFR_BASE(0x1F0 + 8 * x)
+#  define CAN_FILOBJ_SID_BITS		11
+#  define CAN_FILOBJ_SID_SHIFT		0
+#  define CAN_FILOBJ_SID_MASK					\
+	GENMASK(CAN_FILOBJ_SID_SHIFT + CAN_FILOBJ_SID_BITS - 1, \
+		CAN_FILOBJ_SID_SHIFT)
+#  define CAN_FILOBJ_EID_BITS		18
+#  define CAN_FILOBJ_EID_SHIFT		12
+#  define CAN_FILOBJ_EID_MASK					\
+	GENMASK(CAN_FILOBJ_EID_SHIFT + CAN_FILOBJ_EID_BITS - 1, \
+		CAN_FILOBJ_EID_SHIFT)
+#  define CAN_FILOBJ_SID11		BIT(29)
+#  define CAN_FILOBJ_EXIDE		BIT(30)
+#define CAN_FLTMASK(x)			CAN_SFR_BASE(0x1F4 + 8 * x)
+#  define CAN_FILMASK_MSID_BITS		11
+#  define CAN_FILMASK_MSID_SHIFT	0
+#  define CAN_FILMASK_MSID_MASK					\
+	GENMASK(CAN_FILMASK_MSID_SHIFT + CAN_FILMASK_MSID_BITS - 1, \
+		CAN_FILMASK_MSID_SHIFT)
+#  define CAN_FILMASK_MEID_BITS		18
+#  define CAN_FILMASK_MEID_SHIFT	12
+#  define CAN_FILMASK_MEID_MASK					\
+	GENMASK(CAN_FILMASK_MEID_SHIFT + CAN_FILMASK_MEID_BITS - 1, \
+		CAN_FILMASK_MEID_SHIFT)
+#  define CAN_FILMASK_MSID11		BIT(29)
+#  define CAN_FILMASK_MIDE		BIT(30)
+
+#define CAN_OBJ_ID_SID_BITS		11
+#define CAN_OBJ_ID_SID_SHIFT		0
+#define CAN_OBJ_ID_SID_MASK					\
+	GENMASK(CAN_OBJ_ID_SID_SHIFT + CAN_OBJ_ID_SID_BITS - 1, \
+		CAN_OBJ_ID_SID_SHIFT)
+#define CAN_OBJ_ID_EID_BITS		18
+#define CAN_OBJ_ID_EID_SHIFT		11
+#define CAN_OBJ_ID_EID_MASK					\
+	GENMASK(CAN_OBJ_ID_EID_SHIFT + CAN_OBJ_ID_EID_BITS - 1, \
+		CAN_OBJ_ID_EID_SHIFT)
+#define CAN_OBJ_ID_SID_BIT11		BIT(29)
+
+#define CAN_OBJ_FLAGS_DLC_BITS		4
+#define CAN_OBJ_FLAGS_DLC_SHIFT		0
+#define CAN_OBJ_FLAGS_DLC_MASK					      \
+	GENMASK(CAN_OBJ_FLAGS_DLC_SHIFT + CAN_OBJ_FLAGS_DLC_BITS - 1, \
+		CAN_OBJ_FLAGS_DLC_SHIFT)
+#define CAN_OBJ_FLAGS_IDE		BIT(4)
+#define CAN_OBJ_FLAGS_RTR		BIT(5)
+#define CAN_OBJ_FLAGS_BRS		BIT(6)
+#define CAN_OBJ_FLAGS_FDF		BIT(7)
+#define CAN_OBJ_FLAGS_ESI		BIT(8)
+#define CAN_OBJ_FLAGS_SEQ_BITS		7
+#define CAN_OBJ_FLAGS_SEQ_SHIFT		9
+#define CAN_OBJ_FLAGS_SEQ_MASK					      \
+	GENMASK(CAN_OBJ_FLAGS_SEQ_SHIFT + CAN_OBJ_FLAGS_SEQ_BITS - 1, \
+		CAN_OBJ_FLAGS_SEQ_SHIFT)
+#define CAN_OBJ_FLAGS_FILHIT_BITS	11
+#define CAN_OBJ_FLAGS_FILHIT_SHIFT	5
+#define CAN_OBJ_FLAGS_FILHIT_MASK				      \
+	GENMASK(CAN_FLAGS_FILHIT_SHIFT + CAN_FLAGS_FILHIT_BITS - 1, \
+		CAN_FLAGS_FILHIT_SHIFT)
+
+#define CAN_OBJ_FLAGS_CUSTOM_ISTEF	BIT(31)
+
+#define MCP25XXFD_BUFFER_TXRX_SIZE 2048
+
+static const char * const mcp25xxfd_mode_names[] = {
+	[CAN_CON_MODE_MIXED] = "can2.0+canfd",
+	[CAN_CON_MODE_SLEEP] = "sleep",
+	[CAN_CON_MODE_INTERNAL_LOOPBACK] = "internal loopback",
+	[CAN_CON_MODE_LISTENONLY] = "listen only",
+	[CAN_CON_MODE_CONFIG] = "config",
+	[CAN_CON_MODE_EXTERNAL_LOOPBACK] = "external loopback",
+	[CAN_CON_MODE_CAN2_0] = "can2.0",
+	[CAN_CON_MODE_RESTRICTED] = "restricted"
+};
+
+struct mcp25xxfd_obj {
+	u32 id;
+	u32 flags;
+};
+
+struct mcp25xxfd_obj_tx {
+	struct mcp25xxfd_obj header;
+	u32 data[];
+};
+
+static void mcp25xxfd_obj_to_le(struct mcp25xxfd_obj *obj)
+{
+	obj->id = cpu_to_le32(obj->id);
+	obj->flags = cpu_to_le32(obj->flags);
+}
+
+struct mcp25xxfd_obj_ts {
+	u32 id;
+	u32 flags;
+	u32 ts;
+};
+
+struct mcp25xxfd_obj_tef {
+	struct mcp25xxfd_obj_ts header;
+};
+
+struct mcp25xxfd_obj_rx {
+	struct mcp25xxfd_obj_ts header;
+	u8 data[];
+};
+
+static void mcp25xxfd_obj_ts_from_le(struct mcp25xxfd_obj_ts *obj)
+{
+	obj->id = le32_to_cpu(obj->id);
+	obj->flags = le32_to_cpu(obj->flags);
+	obj->ts = le32_to_cpu(obj->ts);
+}
+
+#define FIFO_DATA(x)			(0x400 + (x))
+#define FIFO_DATA_SIZE			0x800
+
+static const struct can_bittiming_const mcp25xxfd_nominal_bittiming_const = {
+	.name		= DEVICE_NAME,
+	.tseg1_min	= 2,
+	.tseg1_max	= BIT(CAN_NBTCFG_TSEG1_BITS),
+	.tseg2_min	= 1,
+	.tseg2_max	= BIT(CAN_NBTCFG_TSEG2_BITS),
+	.sjw_max	= BIT(CAN_NBTCFG_SJW_BITS),
+	.brp_min	= 1,
+	.brp_max	= BIT(CAN_NBTCFG_BRP_BITS),
+	.brp_inc	= 1,
+};
+
+static const struct can_bittiming_const mcp25xxfd_data_bittiming_const = {
+	.name		= DEVICE_NAME,
+	.tseg1_min	= 1,
+	.tseg1_max	= BIT(CAN_DBTCFG_TSEG1_BITS),
+	.tseg2_min	= 1,
+	.tseg2_max	= BIT(CAN_DBTCFG_TSEG2_BITS),
+	.sjw_max	= BIT(CAN_DBTCFG_SJW_BITS),
+	.brp_min	= 1,
+	.brp_max	= BIT(CAN_DBTCFG_BRP_BITS),
+	.brp_inc	= 1,
+};
+
+enum mcp25xxfd_model {
+	CAN_MCP2517FD	= 0x2517,
+};
+
+enum mcp25xxfd_gpio_mode {
+	gpio_mode_int		= 0,
+	gpio_mode_standby	= MCP25XXFD_IOCON_XSTBYEN,
+	gpio_mode_out_low	= MCP25XXFD_IOCON_PM0,
+	gpio_mode_out_high	= MCP25XXFD_IOCON_PM0 | MCP25XXFD_IOCON_LAT0,
+	gpio_mode_in		= MCP25XXFD_IOCON_PM0 | MCP25XXFD_IOCON_TRIS0
+};
+
+struct mcp25xxfd_trigger_tx_message {
+	struct spi_message msg;
+	struct spi_transfer fill_xfer;
+	struct spi_transfer trigger_xfer;
+	int fifo;
+	char fill_cmd[2];
+	char fill_obj[sizeof(struct mcp25xxfd_obj_tx)];
+	char fill_data[64];
+	char trigger_cmd[2];
+	char trigger_data;
+};
+
+struct mcp25xxfd_read_fifo_info {
+	struct mcp25xxfd_obj_ts *rxb[32];
+	int rx_count;
+};
+
+struct mcp25xxfd_priv {
+	struct can_priv	   can;
+	struct net_device *net;
+	struct spi_device *spi;
+	struct regulator *power;
+	struct regulator *transceiver;
+	struct clk *clk;
+
+	struct mutex clk_user_lock; /* lock for enabling/disabling the clock */
+	int clk_user_mask;
+#define MCP25XXFD_CLK_USER_CAN BIT(0)
+
+	struct dentry *debugfs_dir;
+
+	/* the actual model of the mcp25xxfd */
+	enum mcp25xxfd_model model;
+
+	struct {
+		/* clock configuration */
+		bool clock_pll;
+		bool clock_div2;
+		int  clock_odiv;
+
+		/* GPIO configuration */
+		bool gpio_opendrain;
+	} config;
+
+	/* the distinct spi_speeds to use for spi communication */
+	u32 spi_setup_speed_hz;
+	u32 spi_speed_hz;
+
+	/* fifo info */
+	struct {
+		/* define payload size and mode */
+		int payload_size;
+		u32 payload_mode;
+
+		/* TEF addresses - start, end and current */
+		u32 tef_fifos;
+		u32 tef_address_start;
+		u32 tef_address_end;
+		u32 tef_address;
+
+		/* address in mcp25xxfd-Fifo RAM of each fifo */
+		u32 fifo_address[32];
+
+		/* infos on tx-fifos */
+		u32 tx_fifos;
+		u32 tx_fifo_start;
+		u32 tx_fifo_mask; /* bitmask of which fifo is a tx fifo */
+		u32 tx_submitted_mask;
+		u32 tx_pending_mask;
+		u32 tx_pending_mask_in_irq;
+		u32 tx_processed_mask;
+
+		/* info on rx_fifos */
+		u32 rx_fifos;
+		u32 rx_fifo_depth;
+		u32 rx_fifo_start;
+		u32 rx_fifo_mask;  /* bitmask of which fifo is a rx fifo */
+
+		/* memory image of FIFO RAM on mcp25xxfd */
+		u8 fifo_data[MCP25XXFD_BUFFER_TXRX_SIZE];
+
+	} fifos;
+
+	/* structure with active fifos that need to get fed to the system */
+	struct mcp25xxfd_read_fifo_info queued_fifos;
+
+	/* statistics */
+	struct {
+		/* number of calls to the irq handler */
+		u64 irq_calls;
+		/* number of loops inside the irq handler */
+		u64 irq_loops;
+
+		/* interrupt handler state and statistics */
+		u32 irq_state;
+#define IRQ_STATE_NEVER_RUN 0
+#define IRQ_STATE_RUNNING 1
+#define IRQ_STATE_HANDLED 2
+		/* stats on number of rx overflows */
+		u64 rx_overflow;
+		/* statistics of FIFO usage */
+		u64 fifo_usage[32];
+
+		/* message abort counter */
+		u64 rx_mab;
+		u64 tx_mab;
+
+		/* message counter fd */
+		u64 rx_fd_count;
+		u64 tx_fd_count;
+
+		/* message counter fd bit rate switch */
+		u64 rx_brs_count;
+		u64 tx_brs_count;
+
+		/* interrupt counter */
+		u64 int_ivm_count;
+		u64 int_wake_count;
+		u64 int_cerr_count;
+		u64 int_serr_count;
+		u64 int_rxov_count;
+		u64 int_txat_count;
+		u64 int_spicrc_count;
+		u64 int_ecc_count;
+		u64 int_tef_count;
+		u64 int_mod_count;
+		u64 int_tbc_count;
+		u64 int_rx_count;
+		u64 int_tx_count;
+
+		/* dlc statistics */
+		u64 rx_dlc_usage[16];
+		u64 tx_dlc_usage[16];
+	} stats;
+
+	/* the current status of the mcp25xxfd */
+	struct {
+		u32 intf;
+		/* ASSERT(CAN_INT + 4 == CAN_RXIF) */
+		u32 rxif;
+		/* ASSERT(CAN_RXIF + 4 == CAN_TXIF) */
+		u32 txif;
+		/* ASSERT(CAN_TXIF + 4 == CAN_RXOVIF) */
+		u32 rxovif;
+		/* ASSERT(CAN_RXOVIF + 4 == CAN_TXATIF) */
+		u32 txatif;
+		/* ASSERT(CAN_TXATIF + 4 == CAN_TXREQ) */
+		u32 txreq;
+		/* ASSERT(CAN_TXREQ + 4 == CAN_TREC) */
+		u32 trec;
+		/* ASSERT(CAN_TREC + 4 == CAN_BDIAG0) */
+		u32 bdiag0;
+		/* ASSERT(CAN_BDIAG0 + 4 == CAN_BDIAG1) */
+		u32 bdiag1;
+	} status;
+
+	/* configuration registers */
+	struct {
+		u32 osc;
+		u32 ecccon;
+		u32 con;
+		u32 iocon;
+		u32 tdc;
+		u32 tscon;
+		u32 tefcon;
+		u32 nbtcfg;
+		u32 dbtcfg;
+	} regs;
+
+	/* interrupt handler signaling */
+	int force_quit;
+	int after_suspend;
+#define AFTER_SUSPEND_UP 1
+#define AFTER_SUSPEND_DOWN 2
+#define AFTER_SUSPEND_POWER 4
+#define AFTER_SUSPEND_RESTART 8
+	int restart_tx;
+
+	/* interrupt flags during irq handling */
+	u32 bdiag1_clear_mask;
+	u32 bdiag1_clear_value;
+
+	/* composit error id and dataduring irq handling */
+	u32 can_err_id;
+	u32 can_err_data[8];
+
+	/* the current mode */
+	u32 active_can_mode;
+	u32 new_state;
+
+	/* status of the tx_queue enabled/disabled */
+	u32 tx_queue_status;
+#define TX_QUEUE_STATUS_INIT		0
+#define TX_QUEUE_STATUS_RUNNING		1
+#define TX_QUEUE_STATUS_NEEDS_START	2
+#define TX_QUEUE_STATUS_STOPPED		3
+
+	/* spi-tx/rx buffers for efficient transfers
+	 * used during setup and irq
+	 */
+	struct mutex spi_rxtx_lock;
+	u8 spi_tx[MCP25XXFD_BUFFER_TXRX_SIZE];
+	u8 spi_rx[MCP25XXFD_BUFFER_TXRX_SIZE];
+
+	/* structure for transmit fifo spi_messages */
+	struct mcp25xxfd_trigger_tx_message *spi_transmit_fifos;
+};
+
+/* module parameters */
+bool use_bulk_release_fifos;
+module_param(use_bulk_release_fifos, bool, 0664);
+MODULE_PARM_DESC(use_bulk_release_fifos,
+		 "Use code that favours longer spi transfers over multiple transfers");
+bool use_complete_fdfifo_read;
+module_param(use_complete_fdfifo_read, bool, 0664);
+MODULE_PARM_DESC(use_complete_fdfifo_read,
+		 "Use code that favours longer spi transfers over multiple transfers for fd can");
+unsigned int tx_fifos;
+module_param(tx_fifos, uint, 0664);
+MODULE_PARM_DESC(tx_fifos,
+		 "Number of tx-fifos to configure\n");
+unsigned int bw_sharing_log2bits;
+module_param(bw_sharing_log2bits, uint, 0664);
+MODULE_PARM_DESC(bw_sharing_log2bits,
+		 "Delay between 2 transmissions in number of arbitration bit times\n");
+bool three_shot;
+module_param(three_shot, bool, 0664);
+MODULE_PARM_DESC(three_shot,
+		 "Use 3 shots when one-shot is requested");
+
+/* spi sync helper */
+
+/* wrapper arround spi_sync, that sets speed_hz */
+static int mcp25xxfd_sync_transfer(struct spi_device *spi,
+				   struct spi_transfer *xfer,
+				   unsigned int xfers,
+				   int speed_hz)
+{
+	int i;
+
+	for (i = 0; i < xfers; i++)
+		xfer[i].speed_hz = speed_hz;
+
+	return spi_sync_transfer(spi, xfer, xfers);
+}
+
+/* an optimization of spi_write_then_read that merges the transfers */
+static int mcp25xxfd_write_then_read(struct spi_device *spi,
+				     const void *tx_buf,
+				     unsigned int tx_len,
+				     void *rx_buf,
+				     unsigned int rx_len,
+				     int speed_hz)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct spi_transfer xfer[2];
+	u8 single_reg_data_tx[6];
+	u8 single_reg_data_rx[6];
+	int ret;
+
+	memset(xfer, 0, sizeof(xfer));
+
+	/* when using a halfduplex controller or to big for buffer */
+	if ((spi->master->flags & SPI_MASTER_HALF_DUPLEX) ||
+	    (tx_len + rx_len > sizeof(priv->spi_tx))) {
+		xfer[0].tx_buf = tx_buf;
+		xfer[0].len = tx_len;
+
+		xfer[1].rx_buf = rx_buf;
+		xfer[1].len = rx_len;
+
+		return mcp25xxfd_sync_transfer(spi, xfer, 2, speed_hz);
+	}
+
+	/* full duplex optimization */
+	xfer[0].len = tx_len + rx_len;
+	if (xfer[0].len > sizeof(single_reg_data_tx)) {
+		mutex_lock(&priv->spi_rxtx_lock);
+		xfer[0].tx_buf = priv->spi_tx;
+		xfer[0].rx_buf = priv->spi_rx;
+	} else {
+		xfer[0].tx_buf = single_reg_data_tx;
+		xfer[0].rx_buf = single_reg_data_rx;
+	}
+
+	/* copy and clean */
+	memcpy((u8 *)xfer[0].tx_buf, tx_buf, tx_len);
+	memset((u8 *)xfer[0].tx_buf + tx_len, 0, rx_len);
+
+	ret = mcp25xxfd_sync_transfer(spi, xfer, 1, speed_hz);
+	if (!ret)
+		memcpy(rx_buf, xfer[0].rx_buf + tx_len, rx_len);
+
+	if (xfer[0].len > sizeof(single_reg_data_tx))
+		mutex_unlock(&priv->spi_rxtx_lock);
+
+	return ret;
+}
+
+/* simple spi_write wrapper with speed_hz */
+static int mcp25xxfd_write(struct spi_device *spi,
+			   const void *tx_buf,
+			   unsigned int tx_len,
+			   int speed_hz)
+{
+	struct spi_transfer xfer;
+
+	memset(&xfer, 0, sizeof(xfer));
+	xfer.tx_buf = tx_buf;
+	xfer.len = tx_len;
+
+	return mcp25xxfd_sync_transfer(spi, &xfer, 1, speed_hz);
+}
+
+/* spi_sync wrapper similar to spi_write_then_read that optimizes transfers */
+static int mcp25xxfd_write_then_write(struct spi_device *spi,
+				      const void *tx_buf,
+				      unsigned int tx_len,
+				      const void *tx2_buf,
+				      unsigned int tx2_len,
+				      int speed_hz)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct spi_transfer xfer;
+	u8 single_reg_data[6];
+	int ret;
+
+	if (tx_len + tx2_len > MCP25XXFD_BUFFER_TXRX_SIZE)
+		return -EINVAL;
+
+	memset(&xfer, 0, sizeof(xfer));
+
+	xfer.len = tx_len + tx2_len;
+	if (xfer.len > sizeof(single_reg_data)) {
+		mutex_lock(&priv->spi_rxtx_lock);
+		xfer.tx_buf = priv->spi_tx;
+	} else {
+		xfer.tx_buf = single_reg_data;
+	}
+
+	memcpy((u8 *)xfer.tx_buf, tx_buf, tx_len);
+	memcpy((u8 *)xfer.tx_buf + tx_len, tx2_buf, tx2_len);
+
+	ret = mcp25xxfd_sync_transfer(spi, &xfer, 1, speed_hz);
+
+	if (xfer.len > sizeof(single_reg_data))
+		mutex_unlock(&priv->spi_rxtx_lock);
+
+	return ret;
+}
+
+/* mcp25xxfd spi command/protocol helper */
+
+static void mcp25xxfd_calc_cmd_addr(u16 cmd, u16 addr, u8 *data)
+{
+	cmd = cmd | (addr & ADDRESS_MASK);
+
+	data[0] = (cmd >> 8) & 0xff;
+	data[1] = (cmd >> 0) & 0xff;
+}
+
+static int mcp25xxfd_cmd_reset(struct spi_device *spi, u32 speed_hz)
+{
+	u8 cmd[2];
+
+	mcp25xxfd_calc_cmd_addr(INSTRUCTION_RESET, 0, cmd);
+
+	/* write the reset command */
+	return mcp25xxfd_write(spi, cmd, 2, speed_hz);
+}
+
+/* read multiple bytes, transform some registers */
+static int mcp25xxfd_cmd_readn(struct spi_device *spi, u32 reg,
+			       void *data, int n, u32 speed_hz)
+{
+	u8 cmd[2];
+	int ret;
+
+	mcp25xxfd_calc_cmd_addr(INSTRUCTION_READ, reg, cmd);
+
+	ret = mcp25xxfd_write_then_read(spi, &cmd, 2, data, n, speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mcp25xxfd_convert_to_cpu(u32 *data, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++)
+		data[i] = le32_to_cpu(data[i]);
+
+	return 0;
+}
+
+static int mcp25xxfd_first_byte(u32 mask)
+{
+	return (mask & 0x0000ffff) ?
+		((mask & 0x000000ff) ? 0 : 1) :
+		((mask & 0x00ff0000) ? 2 : 3);
+}
+
+static int mcp25xxfd_last_byte(u32 mask)
+{
+	return (mask & 0xffff0000) ?
+		((mask & 0xff000000) ? 3 : 2) :
+		((mask & 0x0000ff00) ? 1 : 0);
+}
+
+/* read a register, but we are only interrested in a few bytes */
+static int mcp25xxfd_cmd_read_mask(struct spi_device *spi, u32 reg,
+				   u32 *data, u32 mask, u32 speed_hz)
+{
+	int first_byte, last_byte, len_byte;
+	int ret;
+
+	/* check that at least one bit is set */
+	if (!mask)
+		return -EINVAL;
+
+	/* calculate first and last byte used */
+	first_byte = mcp25xxfd_first_byte(mask);
+	last_byte = mcp25xxfd_last_byte(mask);
+	len_byte = last_byte - first_byte + 1;
+
+	/* do a partial read */
+	*data = 0;
+	ret = mcp25xxfd_cmd_readn(spi, reg + first_byte,
+				  ((void *)data + first_byte), len_byte,
+				  speed_hz);
+	if (ret)
+		return ret;
+
+	return mcp25xxfd_convert_to_cpu(data, 1);
+}
+
+static int mcp25xxfd_cmd_read(struct spi_device *spi, u32 reg, u32 *data,
+			      u32 speed_hz)
+{
+	return mcp25xxfd_cmd_read_mask(spi, reg, data, -1, speed_hz);
+}
+
+/* read a register, but we are only interrested in a few bytes */
+static int mcp25xxfd_cmd_write_mask(struct spi_device *spi, u32 reg,
+				    u32 data, u32 mask, u32 speed_hz)
+{
+	int first_byte, last_byte, len_byte;
+	u8 cmd[2];
+
+	/* check that at least one bit is set */
+	if (!mask)
+		return -EINVAL;
+
+	/* calculate first and last byte used */
+	first_byte = mcp25xxfd_first_byte(mask);
+	last_byte = mcp25xxfd_last_byte(mask);
+	len_byte = last_byte - first_byte + 1;
+
+	/* prepare buffer */
+	mcp25xxfd_calc_cmd_addr(INSTRUCTION_WRITE, reg + first_byte, cmd);
+	data = cpu_to_le32(data);
+
+	return mcp25xxfd_write_then_write(spi,
+					  cmd, sizeof(cmd),
+					  ((void *)&data + first_byte),
+					  len_byte,
+					  speed_hz);
+}
+
+static int mcp25xxfd_cmd_write(struct spi_device *spi, u32 reg, u32 data,
+			       u32 speed_hz)
+{
+	return mcp25xxfd_cmd_write_mask(spi, reg, data, -1, speed_hz);
+}
+
+static int mcp25xxfd_cmd_writen(struct spi_device *spi, u32 reg,
+				void *data, int n, u32 speed_hz)
+{
+	u8 cmd[2];
+	int ret;
+
+	mcp25xxfd_calc_cmd_addr(INSTRUCTION_WRITE, reg, cmd);
+
+	ret = mcp25xxfd_write_then_write(spi, &cmd, 2, data, n, speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mcp25xxfd_clean_sram(struct spi_device *spi, u32 speed_hz)
+{
+	u8 buffer[256];
+	int i;
+	int ret;
+
+	memset(buffer, 0, sizeof(buffer));
+
+	for (i = 0; i < FIFO_DATA_SIZE; i += sizeof(buffer)) {
+		ret = mcp25xxfd_cmd_writen(spi, FIFO_DATA(i),
+					   buffer, sizeof(buffer),
+					   speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* mcp25xxfd opmode helper functions */
+
+static int mcp25xxfd_get_opmode(struct spi_device *spi,
+				int *mode,
+				int speed_hz)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+
+	/* read the mode */
+	ret = mcp25xxfd_cmd_read_mask(spi,
+				      CAN_CON,
+				      &priv->regs.con,
+				      CAN_CON_OPMOD_MASK,
+				      speed_hz);
+	if (ret)
+		return ret;
+	/* calculate the mode */
+	*mode = (priv->regs.con & CAN_CON_OPMOD_MASK) >>
+		CAN_CON_OPMOD_SHIFT;
+
+	/* and assign to active mode as well */
+	priv->active_can_mode = *mode;
+
+	return 0;
+}
+
+static int mcp25xxfd_set_opmode(struct spi_device *spi, int mode,
+				int speed_hz)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 val = priv->regs.con & ~CAN_CON_REQOP_MASK;
+
+	/* regs.con also contains the effective register */
+	priv->regs.con = val |
+		(mode << CAN_CON_REQOP_SHIFT) |
+		(mode << CAN_CON_OPMOD_SHIFT);
+	priv->active_can_mode = mode;
+
+	/* if the opmode is sleep then the oscilator will be disabled
+	 * and also not ready
+	 */
+	if (mode == CAN_CON_MODE_SLEEP) {
+		priv->regs.osc &= ~(MCP25XXFD_OSC_OSCRDY |
+				    MCP25XXFD_OSC_PLLRDY |
+				    MCP25XXFD_OSC_SCLKRDY);
+		priv->regs.osc |= MCP25XXFD_OSC_OSCDIS;
+	}
+
+	/* but only write the relevant section */
+	return mcp25xxfd_cmd_write_mask(spi, CAN_CON,
+					priv->regs.con,
+					CAN_CON_REQOP_MASK,
+					speed_hz);
+}
+
+static int mcp25xxfd_set_normal_opmode(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int mode;
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_LOOPBACK)
+		mode = CAN_CON_MODE_EXTERNAL_LOOPBACK;
+	else if (priv->can.ctrlmode & CAN_CTRLMODE_LISTENONLY)
+		mode = CAN_CON_MODE_LISTENONLY;
+	else if (priv->can.ctrlmode & CAN_CTRLMODE_FD)
+		mode = CAN_CON_MODE_MIXED;
+	else
+		mode = CAN_CON_MODE_CAN2_0;
+
+	return mcp25xxfd_set_opmode(spi, mode, priv->spi_setup_speed_hz);
+}
+
+/* clock helper */
+static int mcp25xxfd_wake_from_sleep(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 waitfor = MCP25XXFD_OSC_OSCRDY;
+	u32 mask = waitfor | MCP25XXFD_OSC_OSCDIS;
+	unsigned long timeout;
+	int ret;
+
+	/* write clock with OSCDIS cleared*/
+	priv->regs.osc &= ~MCP25XXFD_OSC_OSCDIS;
+	ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_OSC,
+				  priv->regs.osc,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* wait for synced pll/osc/sclk */
+	timeout = jiffies + MCP25XXFD_OSC_POLLING_JIFFIES;
+	while (time_before_eq(jiffies, timeout)) {
+		ret = mcp25xxfd_cmd_read(spi, MCP25XXFD_OSC,
+					 &priv->regs.osc,
+					 priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		if ((priv->regs.osc & mask) == waitfor) {
+			priv->active_can_mode = CAN_CON_MODE_CONFIG;
+			return 0;
+		}
+		/* wait some time */
+		mdelay(100);
+	}
+
+	dev_err(&spi->dev,
+		"Clock did not enable within the timeout period\n");
+	return -ETIMEDOUT;
+}
+
+static int mcp25xxfd_hw_check_clock(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 val;
+	int ret;
+
+	/* read the osc register and check if it matches
+	 * what we have on record
+	 */
+	ret = mcp25xxfd_cmd_read(spi, MCP25XXFD_OSC,
+				 &val,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	if (val == priv->regs.osc)
+		return 0;
+
+	/* ignore all those ready bits on second try */
+	if ((val & 0xff) == (priv->regs.osc & 0xff)) {
+		dev_info(&spi->dev,
+			 "The oscillator register value %08x does not match what we expect: %08x - it is still reasonable, but please investigate\n",
+			val, priv->regs.osc);
+		return 0;
+	}
+
+	dev_err(&spi->dev,
+		"The oscillator register value %08x does not match what we expect: %08x\n",
+		val, priv->regs.osc);
+
+	return -ENODEV;
+}
+
+static int mcp25xxfd_start_clock(struct spi_device *spi, int requestor_mask)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int ret = 0;
+
+	mutex_lock(&priv->clk_user_lock);
+
+	priv->clk_user_mask |= requestor_mask;
+
+	if (priv->clk_user_mask != requestor_mask)
+		goto out;
+
+	/* check that the controller clock register
+	 * is what it is supposed to be
+	 */
+	ret = mcp25xxfd_hw_check_clock(spi);
+	if (ret) {
+		dev_err(&spi->dev,
+			"Controller clock register in unexpected state");
+		goto out;
+	}
+
+	/* and we start the clock */
+	if (!IS_ERR(priv->clk))
+		ret = clk_prepare_enable(priv->clk);
+
+	/* we wake from sleep */
+	if (priv->active_can_mode == CAN_CON_MODE_SLEEP)
+		ret = mcp25xxfd_wake_from_sleep(spi);
+
+out:
+	mutex_unlock(&priv->clk_user_lock);
+
+	return ret;
+}
+
+static int mcp25xxfd_stop_clock(struct spi_device *spi, int requestor_mask)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	mutex_lock(&priv->clk_user_lock);
+
+	priv->clk_user_mask &= ~requestor_mask;
+
+	if (!priv->clk_user_mask)
+		goto out;
+
+	/* put us into sleep mode */
+	mcp25xxfd_set_opmode(spi, CAN_CON_MODE_SLEEP,
+			     priv->spi_setup_speed_hz);
+
+	/* and we stop the clock */
+	if (!IS_ERR(priv->clk))
+		clk_disable_unprepare(priv->clk);
+
+out:
+	mutex_unlock(&priv->clk_user_lock);
+
+	return 0;
+}
+
+/* ideally these would be defined in uapi/linux/can.h */
+#define CAN_EFF_SID_SHIFT		(CAN_EFF_ID_BITS - CAN_SFF_ID_BITS)
+#define CAN_EFF_SID_BITS		CAN_SFF_ID_BITS
+#define CAN_EFF_SID_MASK				      \
+	GENMASK(CAN_EFF_SID_SHIFT + CAN_EFF_SID_BITS - 1,     \
+		CAN_EFF_SID_SHIFT)
+#define CAN_EFF_EID_SHIFT		0
+#define CAN_EFF_EID_BITS		CAN_EFF_SID_SHIFT
+#define CAN_EFF_EID_MASK				      \
+	GENMASK(CAN_EFF_EID_SHIFT + CAN_EFF_EID_BITS - 1,     \
+		CAN_EFF_EID_SHIFT)
+
+static void mcp25xxfd_canid_to_mcpid(u32 can_id, u32 *id, u32 *flags)
+{
+	if (can_id & CAN_EFF_FLAG) {
+		int sid = (can_id & CAN_EFF_SID_MASK) >> CAN_EFF_SID_SHIFT;
+		int eid = (can_id & CAN_EFF_EID_MASK) >> CAN_EFF_EID_SHIFT;
+		*id = (eid << CAN_OBJ_ID_EID_SHIFT) |
+			(sid << CAN_OBJ_ID_SID_SHIFT);
+		*flags = CAN_OBJ_FLAGS_IDE;
+	} else {
+		*id = can_id & CAN_SFF_MASK;
+		*flags = 0;
+	}
+
+	*flags |= (can_id & CAN_RTR_FLAG) ? CAN_OBJ_FLAGS_RTR : 0;
+}
+
+static void mcp25xxfd_mcpid_to_canid(u32 mcpid, u32 mcpflags, u32 *id)
+{
+	u32 sid = (mcpid & CAN_OBJ_ID_SID_MASK) >> CAN_OBJ_ID_SID_SHIFT;
+	u32 eid = (mcpid & CAN_OBJ_ID_EID_MASK) >> CAN_OBJ_ID_EID_SHIFT;
+
+	if (mcpflags & CAN_OBJ_FLAGS_IDE) {
+		*id = (eid << CAN_EFF_EID_SHIFT) |
+			(sid << CAN_EFF_SID_SHIFT) |
+			CAN_EFF_FLAG;
+	} else {
+		*id = sid;
+	}
+
+	*id |= (mcpflags & CAN_OBJ_FLAGS_RTR) ? CAN_RTR_FLAG : 0;
+}
+
+static void __mcp25xxfd_stop_queue(struct net_device *net,
+				   unsigned int id)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+
+	if (priv->tx_queue_status >= TX_QUEUE_STATUS_STOPPED)
+		dev_warn(&priv->spi->dev,
+			 "tx-queue is already stopped by: %i\n",
+			 priv->tx_queue_status);
+
+	priv->tx_queue_status = id ? id : TX_QUEUE_STATUS_STOPPED;
+	netif_stop_queue(priv->net);
+}
+
+/* helper to identify who is stopping the queue by line number */
+#define mcp25xxfd_stop_queue(spi) \
+	__mcp25xxfd_stop_queue(spi, __LINE__)
+
+static void mcp25xxfd_wake_queue(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* nothing should be left pending /in flight now... */
+	priv->fifos.tx_pending_mask = 0;
+	priv->fifos.tx_submitted_mask = 0;
+	priv->fifos.tx_processed_mask = 0;
+	priv->tx_queue_status = TX_QUEUE_STATUS_RUNNING;
+
+	/* wake queue now */
+	netif_wake_queue(priv->net);
+}
+
+/* CAN transmit related*/
+
+static void mcp25xxfd_mark_tx_pending(void *context)
+{
+	struct mcp25xxfd_trigger_tx_message *txm = context;
+	struct spi_device *spi = txm->msg.spi;
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* only here or in the irq handler this value is changed,
+	 * so there is no race condition and it does not require locking
+	 * serialization happens via spi_pump_message
+	 */
+	priv->fifos.tx_pending_mask |= BIT(txm->fifo);
+}
+
+static int mcp25xxfd_fill_spi_transmit_fifos(struct mcp25xxfd_priv *priv)
+{
+	struct mcp25xxfd_trigger_tx_message *txm;
+	int i, fifo;
+	const u32 trigger = CAN_FIFOCON_TXREQ | CAN_FIFOCON_UINC;
+	const int first_byte = mcp25xxfd_first_byte(trigger);
+	u32 fifo_address;
+
+	priv->spi_transmit_fifos = kcalloc(priv->fifos.tx_fifos,
+					   sizeof(*priv->spi_transmit_fifos),
+					   GFP_KERNEL | GFP_DMA);
+	if (!priv->spi_transmit_fifos)
+		return -ENOMEM;
+
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		fifo = priv->fifos.tx_fifo_start + i;
+		txm = &priv->spi_transmit_fifos[i];
+		fifo_address = priv->fifos.fifo_address[fifo];
+		/* prepare the message */
+		spi_message_init(&txm->msg);
+		txm->msg.complete = mcp25xxfd_mark_tx_pending;
+		txm->msg.context = txm;
+		txm->fifo = fifo;
+		/* the payload itself */
+		txm->fill_xfer.speed_hz = priv->spi_speed_hz;
+		txm->fill_xfer.tx_buf = txm->fill_cmd;
+		txm->fill_xfer.len = 2;
+		txm->fill_xfer.cs_change = true;
+		mcp25xxfd_calc_cmd_addr(INSTRUCTION_WRITE,
+					FIFO_DATA(fifo_address),
+					txm->fill_cmd);
+		spi_message_add_tail(&txm->fill_xfer, &txm->msg);
+		/* the trigger command */
+		txm->trigger_xfer.speed_hz = priv->spi_speed_hz;
+		txm->trigger_xfer.tx_buf = txm->trigger_cmd;
+		txm->trigger_xfer.len = 3;
+		mcp25xxfd_calc_cmd_addr(INSTRUCTION_WRITE,
+					CAN_FIFOCON(fifo) + first_byte,
+					txm->trigger_cmd);
+		txm->trigger_data = trigger >> (8 * first_byte);
+		spi_message_add_tail(&txm->trigger_xfer, &txm->msg);
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_transmit_message_common(struct spi_device *spi,
+					     int fifo,
+					     struct mcp25xxfd_obj_tx *obj,
+					     int len,
+					     u8 *data)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_trigger_tx_message *txm =
+		&priv->spi_transmit_fifos[fifo - priv->fifos.tx_fifo_start];
+	int ret;
+
+	/* add fifo as seq */
+	obj->header.flags |= fifo << CAN_OBJ_FLAGS_SEQ_SHIFT;
+
+	/* transform to le32 */
+	mcp25xxfd_obj_to_le(&obj->header);
+
+	/* fill in details */
+	memcpy(txm->fill_obj, obj, sizeof(struct mcp25xxfd_obj_tx));
+	memset(txm->fill_data, 0, priv->fifos.payload_size);
+	memcpy(txm->fill_data, data, len);
+
+	/* transfers to FIFO RAM has to be multiple of 4 */
+	txm->fill_xfer.len =
+		2 + sizeof(struct mcp25xxfd_obj_tx) + ALIGN(len, 4);
+
+	/* and transmit asyncroniously */
+	ret = spi_async(spi, &txm->msg);
+	if (ret)
+		return NETDEV_TX_BUSY;
+
+	return NETDEV_TX_OK;
+}
+
+static int mcp25xxfd_transmit_fdmessage(struct spi_device *spi, int fifo,
+					struct canfd_frame *frame)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_obj_tx obj;
+	int dlc = can_len2dlc(frame->len);
+	u32 flags;
+
+	frame->len = can_dlc2len(dlc);
+
+	mcp25xxfd_canid_to_mcpid(frame->can_id, &obj.header.id, &flags);
+
+	flags |= dlc << CAN_OBJ_FLAGS_DLC_SHIFT;
+	flags |= (frame->can_id & CAN_EFF_FLAG) ? CAN_OBJ_FLAGS_IDE : 0;
+	flags |= (frame->can_id & CAN_RTR_FLAG) ? CAN_OBJ_FLAGS_RTR : 0;
+	if (frame->flags & CANFD_BRS) {
+		flags |= CAN_OBJ_FLAGS_BRS;
+		priv->stats.tx_brs_count++;
+	}
+	flags |= (frame->flags & CANFD_ESI) ? CAN_OBJ_FLAGS_ESI : 0;
+	flags |= CAN_OBJ_FLAGS_FDF;
+
+	priv->stats.tx_fd_count++;
+	priv->stats.tx_dlc_usage[dlc]++;
+
+	obj.header.flags = flags;
+
+	return mcp25xxfd_transmit_message_common(spi, fifo, &obj,
+						 frame->len, frame->data);
+}
+
+static int mcp25xxfd_transmit_message(struct spi_device *spi, int fifo,
+				      struct can_frame *frame)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_obj_tx obj;
+	u32 flags;
+
+	if (frame->can_dlc > 8)
+		frame->can_dlc = 8;
+
+	priv->stats.tx_dlc_usage[frame->can_dlc]++;
+
+	mcp25xxfd_canid_to_mcpid(frame->can_id, &obj.header.id, &flags);
+
+	flags |= frame->can_dlc << CAN_OBJ_FLAGS_DLC_SHIFT;
+	flags |= (frame->can_id & CAN_EFF_FLAG) ? CAN_OBJ_FLAGS_IDE : 0;
+	flags |= (frame->can_id & CAN_RTR_FLAG) ? CAN_OBJ_FLAGS_RTR : 0;
+
+	obj.header.flags = flags;
+
+	return mcp25xxfd_transmit_message_common(spi, fifo, &obj,
+						 frame->can_dlc, frame->data);
+}
+
+static bool mcp25xxfd_is_last_txfifo(struct spi_device *spi,
+				     int fifo)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	return (fifo ==
+		(priv->fifos.tx_fifo_start + priv->fifos.tx_fifos - 1));
+}
+
+static netdev_tx_t mcp25xxfd_start_xmit(struct sk_buff *skb,
+					struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	struct spi_device *spi = priv->spi;
+	u32 pending_mask;
+	int fifo;
+	int ret;
+
+	if (can_dropped_invalid_skb(net, skb))
+		return NETDEV_TX_OK;
+
+	if (priv->can.state == CAN_STATE_BUS_OFF) {
+		mcp25xxfd_stop_queue(priv->net);
+		return NETDEV_TX_BUSY;
+	}
+
+	/* get effective mask */
+	pending_mask = priv->fifos.tx_pending_mask |
+		priv->fifos.tx_submitted_mask;
+
+	/* decide on fifo to assign */
+	if (pending_mask)
+		fifo = fls(pending_mask);
+	else
+		fifo = priv->fifos.tx_fifo_start;
+
+	/* handle error - this should not happen... */
+	if (fifo >= priv->fifos.tx_fifo_start + priv->fifos.tx_fifos) {
+		dev_err(&spi->dev,
+			"reached tx-fifo %i, which is not valid\n",
+			fifo);
+		return NETDEV_TX_BUSY;
+	}
+
+	/* if we are the last one, then stop the queue */
+	if (mcp25xxfd_is_last_txfifo(spi, fifo))
+		mcp25xxfd_stop_queue(priv->net);
+
+	/* mark as submitted */
+	priv->fifos.tx_submitted_mask |= BIT(fifo);
+	priv->stats.fifo_usage[fifo]++;
+
+	/* now process it for real */
+	if (can_is_canfd_skb(skb))
+		ret = mcp25xxfd_transmit_fdmessage(spi, fifo,
+						   (struct canfd_frame *)
+						   skb->data);
+	else
+		ret = mcp25xxfd_transmit_message(spi, fifo,
+						 (struct can_frame *)
+						 skb->data);
+
+	/* keep it for reference until the message really got transmitted */
+	if (ret == NETDEV_TX_OK)
+		can_put_echo_skb(skb, priv->net, fifo);
+
+	return ret;
+}
+
+/* CAN RX Related */
+
+static int mcp25xxfd_can_transform_rx_fd(struct spi_device *spi,
+					 struct mcp25xxfd_obj_rx *rx)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct canfd_frame *frame;
+	struct sk_buff *skb;
+	u32 flags = rx->header.flags;
+	int dlc;
+
+	/* allocate the skb buffer */
+	skb = alloc_canfd_skb(priv->net, &frame);
+	if (!skb) {
+		dev_err(&spi->dev, "cannot allocate RX skb\n");
+		priv->net->stats.rx_dropped++;
+		return -ENOMEM;
+	}
+
+	mcp25xxfd_mcpid_to_canid(rx->header.id, flags, &frame->can_id);
+	frame->flags |= (flags & CAN_OBJ_FLAGS_BRS) ? CANFD_BRS : 0;
+	frame->flags |= (flags & CAN_OBJ_FLAGS_ESI) ? CANFD_ESI : 0;
+
+	dlc = (flags & CAN_OBJ_FLAGS_DLC_MASK) >> CAN_OBJ_FLAGS_DLC_SHIFT;
+	frame->len = can_dlc2len(dlc);
+
+	memcpy(frame->data, rx->data, frame->len);
+
+	priv->stats.rx_fd_count++;
+	priv->net->stats.rx_packets++;
+	priv->net->stats.rx_bytes += frame->len;
+	if (rx->header.flags & CAN_OBJ_FLAGS_BRS)
+		priv->stats.rx_brs_count++;
+	priv->stats.rx_dlc_usage[dlc]++;
+
+	can_led_event(priv->net, CAN_LED_EVENT_RX);
+
+	netif_rx_ni(skb);
+
+	return 0;
+}
+
+static int mcp25xxfd_can_transform_rx_normal(struct spi_device *spi,
+					     struct mcp25xxfd_obj_rx *rx)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct sk_buff *skb;
+	struct can_frame *frame;
+	u32 flags = rx->header.flags;
+	int len;
+	int dlc;
+
+	/* allocate the skb buffer */
+	skb = alloc_can_skb(priv->net, &frame);
+	if (!skb) {
+		dev_err(&spi->dev, "cannot allocate RX skb\n");
+		priv->net->stats.rx_dropped++;
+		return -ENOMEM;
+	}
+
+	mcp25xxfd_mcpid_to_canid(rx->header.id, flags, &frame->can_id);
+
+	dlc = (flags & CAN_OBJ_FLAGS_DLC_MASK) >> CAN_OBJ_FLAGS_DLC_SHIFT;
+	frame->can_dlc = dlc;
+
+	len = can_dlc2len(frame->can_dlc);
+
+	memcpy(frame->data, rx->data, len);
+
+	priv->net->stats.rx_packets++;
+	priv->net->stats.rx_bytes += len;
+	priv->stats.rx_dlc_usage[dlc]++;
+
+	can_led_event(priv->net, CAN_LED_EVENT_RX);
+
+	netif_rx_ni(skb);
+
+	return 0;
+}
+
+static int mcp25xxfd_process_queued_rx(struct spi_device *spi,
+				       struct mcp25xxfd_obj_ts *obj)
+{
+	struct mcp25xxfd_obj_rx *rx = container_of(obj,
+						   struct mcp25xxfd_obj_rx,
+						   header);
+
+	if (obj->flags & CAN_OBJ_FLAGS_FDF)
+		return mcp25xxfd_can_transform_rx_fd(spi, rx);
+	else
+		return mcp25xxfd_can_transform_rx_normal(spi, rx);
+}
+
+static int mcp25xxfd_normal_release_fifos(struct spi_device *spi,
+					  int start, int end)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+
+	/* release each fifo in a separate transfer */
+	for (; start < end ; start++) {
+		ret = mcp25xxfd_cmd_write_mask(spi, CAN_FIFOCON(start),
+					       CAN_FIFOCON_UINC,
+					       CAN_FIFOCON_UINC,
+					       priv->spi_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* unfortunately the CAN_FIFOCON are not directly consecutive
+ * so the optimization of "clearing all in one spi_transfer"
+ * would produce an overhead of 11 unnecessary bytes/fifo
+ * - transferring 14 (2 cmd + 12 data) bytes
+ * instead of just 3 (2 + 1).
+ * On some slower systems this may still be beneficial,
+ * but it is not good enough for the generic case.
+ * On a Raspberry Pi CM the timings for clearing 3 fifos
+ * (at 12.5MHz SPI clock speed) are:
+ * * normal:
+ *   * 3 spi transfers
+ *   * 9 bytes total
+ *   * 36.74us from first CS low to last CS high
+ *   * individual CS: 9.14us, 5.74us and 5.16us
+ *   * 77.02us from CS up of fifo transfer to last release CS up
+ * * bulk:
+ *   * 1 spi transfer
+ *   * 27 bytes total
+ *   * 29.06us CS Low
+ *   * 78.28us from CS up of fifo transfer to last release CS up
+ * this obviously varies with SPI_clock speed
+ * - the slower the clock the less efficient the optimization.
+ * similarly the faster the CPU (and bigger the code cache) the
+ * less effcient the optimization - the above case is border line.
+ */
+
+#define FIFOCON_SPACING (CAN_FIFOCON(1) - CAN_FIFOCON(0))
+#define FIFOCON_SPACINGW (FIFOCON_SPACING / sizeof(u32))
+
+static int mcp25xxfd_bulk_release_fifos(struct spi_device *spi,
+					int start, int end)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int i;
+	int ret;
+
+	/* calculate start address and length */
+	int fifos = end - start;
+	int first_byte = mcp25xxfd_first_byte(CAN_FIFOCON_UINC);
+	int addr = CAN_FIFOCON(start);
+	int len = 1 + (fifos - 1) * FIFOCON_SPACING;
+
+	/* the worsted case buffer */
+	u32 buf[32 * FIFOCON_SPACINGW], base;
+
+	base = (priv->fifos.payload_mode << CAN_FIFOCON_PLSIZE_SHIFT) |
+		((priv->fifos.rx_fifo_depth - 1) << CAN_FIFOCON_FSIZE_SHIFT) |
+		CAN_FIFOCON_RXTSEN | /* RX timestamps */
+		CAN_FIFOCON_UINC |
+		CAN_FIFOCON_TFERFFIE | /* FIFO Full */
+		CAN_FIFOCON_TFHRFHIE | /* FIFO Half Full*/
+		CAN_FIFOCON_TFNRFNIE; /* FIFO not empty */
+
+	memset(buf, 0, sizeof(buf));
+	for (i = 0; i < end - start ; i++) {
+		if (i == priv->fifos.rx_fifos - 1)
+			base |= CAN_FIFOCON_RXOVIE;
+		buf[FIFOCON_SPACINGW * i] = cpu_to_le32(base);
+	}
+
+	ret = mcp25xxfd_cmd_writen(spi, addr + first_byte,
+				   (u8 *)buf + first_byte,
+				   len,
+				   priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* queued FIFO handling for release to system */
+
+static void mcp25xxfd_clear_queued_fifos(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* prepare rfi - mostly used for sorting */
+	priv->queued_fifos.rx_count = 0;
+}
+
+static void mcp25xxfd_addto_queued_fifos(struct spi_device *spi,
+					 struct mcp25xxfd_obj_ts *obj)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_read_fifo_info *rfi = &priv->queued_fifos;
+
+	/* timestamps must ignore the highest byte, so we shift it,
+	 * so that it still compares correctly
+	 */
+	obj->ts <<= 8;
+
+	/* add pointer to queued array-list */
+	rfi->rxb[rfi->rx_count] = obj;
+	rfi->rx_count++;
+}
+
+static int mcp25xxfd_process_queued_tef(struct spi_device *spi,
+					struct mcp25xxfd_obj_ts *obj)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_obj_tef *tef = container_of(obj,
+						     struct mcp25xxfd_obj_tef,
+						     header);
+	int dlc = (obj->flags & CAN_OBJ_FLAGS_DLC_MASK)
+		>> CAN_OBJ_FLAGS_DLC_SHIFT;
+	int fifo = (tef->header.flags & CAN_OBJ_FLAGS_SEQ_MASK) >>
+		CAN_OBJ_FLAGS_SEQ_SHIFT;
+
+	/* update counters */
+	priv->net->stats.tx_packets++;
+	priv->net->stats.tx_bytes += can_dlc2len(dlc);
+	if (obj->flags & CAN_OBJ_FLAGS_FDF)
+		priv->stats.tx_fd_count++;
+	if (obj->flags & CAN_OBJ_FLAGS_BRS)
+		priv->stats.tx_brs_count++;
+	priv->stats.tx_dlc_usage[dlc]++;
+
+	/* release it */
+	can_get_echo_skb(priv->net, fifo);
+
+	can_led_event(priv->net, CAN_LED_EVENT_TX);
+
+	return 0;
+}
+
+static int mcp25xxfd_compare_obj_ts(const void *a, const void *b)
+{
+	const struct mcp25xxfd_obj_ts * const *rxa = a;
+	const struct mcp25xxfd_obj_ts * const *rxb = b;
+	/* using signed here to handle rollover correctly */
+	s32 ats = (*rxa)->ts;
+	s32 bts = (*rxb)->ts;
+
+	if (ats < bts)
+		return -1;
+	if (ats > bts)
+		return 1;
+	return 0;
+}
+
+static int mcp25xxfd_process_queued_fifos(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_read_fifo_info *rfi = &priv->queued_fifos;
+	int i;
+	int ret;
+
+	/* sort the fifos (rx and TEF) by receive timestamp */
+	sort(rfi->rxb, rfi->rx_count, sizeof(struct mcp25xxfd_obj_ts *),
+	     mcp25xxfd_compare_obj_ts, NULL);
+
+	/* process the recived fifos */
+	for (i = 0; i < rfi->rx_count ; i++) {
+		if (rfi->rxb[i]->flags & CAN_OBJ_FLAGS_CUSTOM_ISTEF)
+			ret = mcp25xxfd_process_queued_tef(spi, rfi->rxb[i]);
+		else
+			ret = mcp25xxfd_process_queued_rx(spi, rfi->rxb[i]);
+		if (ret)
+			return ret;
+	}
+
+	/* clear queued fifos */
+	mcp25xxfd_clear_queued_fifos(spi);
+
+	return 0;
+}
+
+static int mcp25xxfd_transform_rx(struct spi_device *spi,
+				  struct mcp25xxfd_obj_rx *rx)
+{
+	int dlc;
+
+	/* transform the data to system byte order */
+	mcp25xxfd_obj_ts_from_le(&rx->header);
+
+	/* add the object to the list */
+	mcp25xxfd_addto_queued_fifos(spi, &rx->header);
+
+	/* calc length and return it */
+	dlc = (rx->header.flags & CAN_OBJ_FLAGS_DLC_MASK)
+		>> CAN_OBJ_FLAGS_DLC_SHIFT;
+	return can_dlc2len(dlc);
+}
+
+/* read_fifo implementations
+ *
+ * read_fifos is a simple implementation, that:
+ *   * loops all fifos
+ *     * read header + some data-bytes (8)
+ *     * read rest of data-bytes bytes
+ *     * release fifo
+ *   for 3 can frames dlc<=8 to read here we have:
+ *     * 6 spi transfers
+ *     * 75 bytes (= 3 * (2 + 12 + 8) bytes + 3 * 3 bytes)
+ *   for 3 canfd frames dlc>8 to read here we have:
+ *     * 9 spi transfers
+ *     * 81 (= 3 * (2 + 12 + 8 + 2) bytes + 3 * 3 bytes) + 3 * extra payload
+ *     this only transfers the required size of bytes on the spi bus.
+ *
+ * bulk_read_fifos is an optimization that is most practical for
+ * Can2.0 busses, but may also be practical for CanFD busses that
+ * have a high average payload data size.
+ *
+ * It will read all of the fifo data in a single spi_transfer:
+ *   * read all fifos in one go (as long as these are ajacent to each other)
+ *   * loop all fifos
+ *     * release fifo
+ *   for 3 can2.0 frames to read here we have:
+ *     * 4 spi transfers
+ *     * 71 bytes (= 2 + 3 * (12 + 8) bytes + 3 * 3 bytes)
+ *   for 3 canfd frames to read here we have:
+ *     * 4 spi transfers
+ *     * 230 bytes (= 2 + 3 * (12 + 64) bytes)
+ *     obviously this reads way too many bytes for framesizes <=32 bytes,
+ *     but it avoids the overhead on the CPU side and may even trigger
+ *     DMA transfers due to the high byte count, which release CPU cycles.
+ *
+ * This optimization will also be efficient for cases where a high
+ * percentage of canFD frames has a dlc-size > 8.
+ * This mode is used for Can2.0 configured busses.
+ *
+ * For now this option can get forced for CanFD via a module parameter.
+ * In the future there may be some heuristics that could trigger a usage
+ * of this mode as well in some circumstances.
+ *
+ * Note: there is a second optimization for release fifo as well,
+ *       but it is not as efficient as this optimization for the
+ *       non-CanFD case - see mcp25xxfd_bulk_release_fifos
+ */
+
+static int mcp25xxfd_read_fifos(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int fifo_header_size = sizeof(struct mcp25xxfd_obj_rx);
+	int fifo_min_payload_size = 8;
+	int fifo_min_size = fifo_header_size + fifo_min_payload_size;
+	int fifo_max_payload_size =
+		((priv->can.ctrlmode & CAN_CTRLMODE_FD) ? 64 : 8);
+	u32 mask = priv->status.rxif;
+	struct mcp25xxfd_obj_rx *rx;
+	int i, len;
+	int ret;
+	u32 fifo_address;
+	u8 *data;
+
+	/* read all the "open" segments in big chunks */
+	for (i = priv->fifos.rx_fifo_start + priv->fifos.rx_fifos - 1;
+	     i >= priv->fifos.rx_fifo_start;
+	     i--) {
+		if (!(mask & BIT(i)))
+			continue;
+		/* the fifo to fill */
+		rx = (struct mcp25xxfd_obj_rx *)
+			(priv->fifos.fifo_data + priv->fifos.fifo_address[i]);
+		/* read the minimal payload */
+		fifo_address = priv->fifos.fifo_address[i];
+		ret = mcp25xxfd_cmd_readn(spi,
+					  FIFO_DATA(fifo_address),
+					  rx,
+					  fifo_min_size,
+					  priv->spi_speed_hz);
+		if (ret)
+			return ret;
+		/* process fifo stats and get length */
+		len = min_t(int, mcp25xxfd_transform_rx(spi, rx),
+			    fifo_max_payload_size);
+
+		/* read extra payload if needed */
+		if (len > fifo_min_payload_size) {
+			data = &rx->data[fifo_min_payload_size];
+			ret = mcp25xxfd_cmd_readn(spi,
+						  FIFO_DATA(fifo_address +
+							    fifo_min_size),
+						  data,
+						  len - fifo_min_payload_size,
+						  priv->spi_speed_hz);
+			if (ret)
+				return ret;
+		}
+		/* release fifo */
+		ret = mcp25xxfd_normal_release_fifos(spi, i, i + 1);
+		if (ret)
+			return ret;
+		/* increment fifo_usage */
+		priv->stats.fifo_usage[i]++;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_bulk_read_fifo_range(struct spi_device *spi,
+					  int start, int end)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	const int fifo_header_size = sizeof(struct mcp25xxfd_obj_rx);
+	const int fifo_max_payload_size = priv->fifos.payload_size;
+	const int fifo_max_size = fifo_header_size + fifo_max_payload_size;
+	struct mcp25xxfd_obj_rx *rx;
+	int i;
+	int ret;
+
+	/* now we got start and end, so read the range */
+	ret = mcp25xxfd_cmd_readn(spi,
+				  FIFO_DATA(priv->fifos.fifo_address[start]),
+				  priv->fifos.fifo_data +
+				  priv->fifos.fifo_address[start],
+				  (end - start) * fifo_max_size,
+				  priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	/* clear all the fifos in range */
+	if (use_bulk_release_fifos)
+		ret = mcp25xxfd_bulk_release_fifos(spi, start, end);
+	else
+		ret = mcp25xxfd_normal_release_fifos(spi, start, end);
+	if (ret)
+		return ret;
+
+	/* preprocess data */
+	for (i = start; i < end ; i++) {
+		/* store the fifo to process */
+		rx = (struct mcp25xxfd_obj_rx *)
+			(priv->fifos.fifo_data + priv->fifos.fifo_address[i]);
+		/* process fifo stats */
+		mcp25xxfd_transform_rx(spi, rx);
+		/* increment usage */
+		priv->stats.fifo_usage[i]++;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_bulk_read_fifos(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 mask = priv->status.rxif;
+	int i, start, end;
+	int ret;
+
+	/* find blocks of set bits top down */
+	for (i = priv->fifos.rx_fifo_start + priv->fifos.rx_fifos - 1;
+	     mask && (i >= priv->fifos.rx_fifo_start);
+	     i--) {
+		/* if the bit is 0 then continue loop to find a 1 */
+		if ((mask & BIT(i)) == 0)
+			continue;
+
+		/* so we found a non-0 bit - this is start and end */
+		start = i;
+		end = i;
+
+		/* find the first bit set */
+		for (; mask & BIT(i); i--) {
+			mask &= ~BIT(i);
+			start = i;
+		}
+
+		/* now process that range */
+		ret = mcp25xxfd_bulk_read_fifo_range(spi, start, end + 1);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_rxif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 mask = priv->status.rxif;
+	int ret;
+
+	if (!mask)
+		return 0;
+
+	/* read all the fifos - for non-fd case use bulk read optimization */
+	if (((priv->can.ctrlmode & CAN_CTRLMODE_FD) == 0) ||
+	    use_complete_fdfifo_read)
+		ret = mcp25xxfd_bulk_read_fifos(spi);
+	else
+		ret = mcp25xxfd_read_fifos(spi);
+
+	return 0;
+}
+
+static void mcp25xxfd_mark_tx_processed(struct spi_device *spi,
+					int fifo)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* set mask */
+	priv->fifos.tx_processed_mask |= BIT(fifo);
+
+	/* check if we should reenable the TX-queue */
+	if (mcp25xxfd_is_last_txfifo(spi, fifo))
+		priv->tx_queue_status = TX_QUEUE_STATUS_NEEDS_START;
+}
+
+static int mcp25xxfd_can_ist_handle_tefif_handle_single(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct mcp25xxfd_obj_tef *tef;
+	int fifo;
+	int ret;
+
+	/* calc address in address space */
+	tef = (struct mcp25xxfd_obj_tef *)(priv->fifos.fifo_data +
+					   priv->fifos.tef_address);
+
+	/* read all the object data */
+	ret = mcp25xxfd_cmd_readn(spi,
+				  FIFO_DATA(priv->fifos.tef_address),
+				  tef,
+				  /* we do not read the last byte of the ts
+				   * to avoid MAB issiues
+				   */
+				  sizeof(*tef) - 1,
+				  priv->spi_speed_hz);
+	/* increment the counter to read next */
+	ret = mcp25xxfd_cmd_write_mask(spi,
+				       CAN_TEFCON,
+				       CAN_TEFCON_UINC,
+				       CAN_TEFCON_UINC,
+				       priv->spi_speed_hz);
+
+	/* transform the data to system byte order */
+	mcp25xxfd_obj_ts_from_le(&tef->header);
+
+	fifo = (tef->header.flags & CAN_OBJ_FLAGS_SEQ_MASK) >>
+		CAN_OBJ_FLAGS_SEQ_SHIFT;
+
+	/* submit to queue */
+	tef->header.flags |= CAN_OBJ_FLAGS_CUSTOM_ISTEF;
+	mcp25xxfd_addto_queued_fifos(spi, &tef->header);
+
+	/* increment tef_address with rollover */
+	priv->fifos.tef_address += sizeof(*tef);
+	if (priv->fifos.tef_address > priv->fifos.tef_address_end)
+		priv->fifos.tef_address =
+			priv->fifos.tef_address_start;
+
+	/* and mark as processed right now */
+	mcp25xxfd_mark_tx_processed(spi, fifo);
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_tefif_conservative(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 val[2];
+	int ret;
+
+	while (1) {
+		/* get the current TEFSTA and TEFUA */
+		ret = mcp25xxfd_cmd_readn(priv->spi,
+					  CAN_TEFSTA,
+					  val,
+					  8,
+					  priv->spi_speed_hz);
+		if (ret)
+			return ret;
+		mcp25xxfd_convert_to_cpu(val, 2);
+
+		/* check for interrupt flags */
+		if (!(val[0] & CAN_TEFSTA_TEFNEIF))
+			return 0;
+
+		if (priv->fifos.tef_address != val[1]) {
+			dev_err(&spi->dev,
+				"TEF Address mismatch - read: %04x calculated: %04x\n",
+				val[1], priv->fifos.tef_address);
+			priv->fifos.tef_address = val[1];
+		}
+
+		ret = mcp25xxfd_can_ist_handle_tefif_handle_single(spi);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_tefif_count(struct spi_device *spi,
+						int count)
+{
+	int i;
+	int ret;
+
+	/* now clear TEF for each */
+	/* TODO: optimize for BULK reads, as we (hopefully) know COUNT */
+	for (i = 0; i < count; i++) {
+		/* handle a single TEF */
+		ret = mcp25xxfd_can_ist_handle_tefif_handle_single(spi);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_tefif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 pending = priv->fifos.tx_pending_mask_in_irq &
+		(~priv->fifos.tx_processed_mask);
+	int count;
+
+	/* calculate the number of fifos that have been processed */
+	count = hweight_long(pending);
+	count -= hweight_long(priv->status.txreq & pending);
+
+	/* in case of unexpected results handle "safely" */
+	if (count <= 0)
+		return mcp25xxfd_can_ist_handle_tefif_conservative(spi);
+
+	return mcp25xxfd_can_ist_handle_tefif_count(spi, count);
+}
+
+static int mcp25xxfd_can_ist_handle_txatif_fifo(struct spi_device *spi,
+						int fifo)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 val;
+	int ret;
+
+	/* read fifo status */
+	ret = mcp25xxfd_cmd_read(spi,
+				 CAN_FIFOSTA(fifo),
+				 &val,
+				 priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	/* clear the relevant interrupt flags */
+	ret = mcp25xxfd_cmd_write_mask(spi,
+				       CAN_FIFOSTA(fifo),
+				       0,
+				       CAN_FIFOSTA_TXABT |
+				       CAN_FIFOSTA_TXLARB |
+				       CAN_FIFOSTA_TXERR |
+				       CAN_FIFOSTA_TXATIF,
+				       priv->spi_speed_hz);
+
+	/* for specific cases we could trigger a retransmit
+	 * instead of an abort.
+	 */
+
+	/* and we release it from the echo_skb buffer
+	 * NOTE: this is one place where packet delivery will not
+	 * be ordered, as we do not have any timing information
+	 * when this occurred
+	 */
+	can_get_echo_skb(priv->net, fifo);
+
+	/* but we need to run a bit of cleanup */
+	priv->status.txif &= ~BIT(fifo);
+	priv->net->stats.tx_aborted_errors++;
+
+	/* mark the fifo as processed */
+	 mcp25xxfd_mark_tx_processed(spi, fifo);
+
+	/* handle all the known cases accordingly - ignoring FIFO full */
+	val &= CAN_FIFOSTA_TXABT |
+		CAN_FIFOSTA_TXLARB |
+		CAN_FIFOSTA_TXERR;
+	switch (val) {
+	case CAN_FIFOSTA_TXERR:
+		break;
+	default:
+		dev_warn_ratelimited(&spi->dev,
+				     "Unknown TX-Fifo abort condition: %08x - stopping tx-queue\n",
+				     val);
+		break;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_txatif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int i, fifo;
+	int ret;
+
+	/* process all the fifos with that flag set */
+	for (i = 0, fifo = priv->fifos.tx_fifo_start;
+	    i < priv->fifos.tx_fifos; i++, fifo++) {
+		if (priv->status.txatif & BIT(fifo)) {
+			ret = mcp25xxfd_can_ist_handle_txatif_fifo(spi, fifo);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void mcp25xxfd_error_skb(struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	struct sk_buff *skb;
+	struct can_frame *frame;
+
+	skb = alloc_can_err_skb(net, &frame);
+	if (skb) {
+		frame->can_id = priv->can_err_id;
+		memcpy(frame->data, priv->can_err_data, 8);
+		netif_rx_ni(skb);
+	} else {
+		netdev_err(net, "cannot allocate error skb\n");
+	}
+}
+
+static int mcp25xxfd_can_ist_handle_rxovif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 mask = priv->status.rxovif;
+	int i;
+	int ret;
+
+	/* clear all fifos that have an overflow bit set */
+	for (i = 0; i < 32; i++) {
+		if (mask & BIT(i)) {
+			ret = mcp25xxfd_cmd_write_mask(spi,
+						       CAN_FIFOSTA(i),
+						       0,
+						       CAN_FIFOSTA_RXOVIF,
+						       priv->spi_speed_hz);
+			if (ret)
+				return ret;
+			/* update statistics */
+			priv->net->stats.rx_over_errors++;
+			priv->net->stats.rx_errors++;
+			priv->stats.rx_overflow++;
+			priv->can_err_id |= CAN_ERR_CRTL;
+			priv->can_err_data[1] |= CAN_ERR_CRTL_RX_OVERFLOW;
+		}
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_modif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int omode = priv->active_can_mode;
+	int mode;
+	int ret;
+
+	/* Note that this irq does not get triggered in all situations
+	 * for example SERRIF will move to RESTICTED or LISTENONLY
+	 * but MODIF will not be raised!
+	 */
+
+	/* get the mode */
+	ret = mcp25xxfd_get_opmode(spi, &mode, priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	/* if we are restricted, then return to "normal" mode */
+	if (mode == CAN_CON_MODE_RESTRICTED)
+		return mcp25xxfd_set_normal_opmode(spi);
+
+	/* the controller itself will transition to sleep, so we ignore it */
+	if (mode == CAN_CON_MODE_SLEEP)
+		return 0;
+
+	/* switches to the same mode as before are also ignored
+	 * - this typically happens if the driver is shortly
+	 *   switching to a different mode and then returning to the
+	 *   original mode
+	 */
+	if (mode == omode)
+		return 0;
+
+	/* these we need to handle correctly, so warn and give context */
+	dev_warn(&spi->dev,
+		 "Controller unexpectedly switched from mode %s(%u) to %s(%u)\n",
+		 mcp25xxfd_mode_names[omode], omode,
+		 mcp25xxfd_mode_names[mode], mode);
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_cerrif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* in principle we could also delay reading bdiag registers
+	 * until we get here - it would add some extra delay in the
+	 * error case, but be slightly faster in the "normal" case.
+	 * slightly faster would be saving 8 bytes of spi transfer.
+	 */
+
+	dev_err_ratelimited(&spi->dev, "CAN Bus error\n");
+	priv->can_err_id |= CAN_ERR_BUSERROR;
+
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DBIT0ERR | CAN_BDIAG1_NBIT0ERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_BIT0;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DBIT0ERR |
+			CAN_BDIAG1_NBIT0ERR;
+	}
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DBIT1ERR | CAN_BDIAG1_NBIT1ERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_BIT1;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DBIT1ERR |
+			CAN_BDIAG1_NBIT1ERR;
+	}
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DSTUFERR | CAN_BDIAG1_NSTUFERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_STUFF;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DSTUFERR |
+			CAN_BDIAG1_NSTUFERR;
+	}
+	if (priv->status.bdiag1 &
+	    (CAN_BDIAG1_DFORMERR | CAN_BDIAG1_NFORMERR)) {
+		priv->can_err_id |= CAN_ERR_BUSERROR;
+		priv->can_err_data[2] |= CAN_ERR_PROT_FORM;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_DFORMERR |
+			CAN_BDIAG1_NFORMERR;
+	}
+	if (priv->status.bdiag1 & CAN_BDIAG1_NACKERR) {
+		priv->can_err_id |= CAN_ERR_ACK;
+		priv->bdiag1_clear_mask |= CAN_BDIAG1_NACKERR;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_eccif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+	u32 val;
+	u32 addr;
+
+	priv->can_err_id |= CAN_ERR_CRTL;
+	priv->can_err_data[1] |= CAN_ERR_CRTL_UNSPEC;
+
+	/* read ECC status register */
+	ret = mcp25xxfd_cmd_read(spi, MCP25XXFD_ECCSTAT, &val,
+				 priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	addr = (val & MCP25XXFD_ECCSTAT_ERRADDR_MASK) >>
+		MCP25XXFD_ECCSTAT_ERRADDR_SHIFT;
+
+	dev_err_ratelimited(&spi->dev,
+			    "ECC %s bit error at %03x\n",
+			    (val & MCP25XXFD_ECCSTAT_DEDIF) ?
+			    "double" : "single",
+			    addr);
+
+	return mcp25xxfd_cmd_write(spi, MCP25XXFD_ECCSTAT, 0,
+				 priv->spi_speed_hz);
+}
+
+static int mcp25xxfd_can_ist_handle_serrif_txmab(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	priv->net->stats.tx_fifo_errors++;
+	priv->net->stats.tx_errors++;
+	priv->stats.tx_mab++;
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_serrif_rxmab(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	priv->net->stats.rx_dropped++;
+	priv->net->stats.rx_errors++;
+	priv->stats.rx_mab++;
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_serrif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 clear;
+	int ret;
+
+	/* clear some interrupts immediately,
+	 * so that we get notified if they happen again
+	 */
+	clear = CAN_INT_SERRIF | CAN_INT_MODIF | CAN_INT_IVMIF;
+	ret = mcp25xxfd_cmd_write_mask(spi, CAN_INT,
+				       priv->status.intf & (~clear),
+				       clear,
+				       priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	/* Errors here are:
+	 * * Bus Bandwidth Error: when a RX Message Assembly Buffer
+	 *   is still full when the next message has already arrived
+	 *   the recived message shall be ignored
+	 * * TX MAB Underflow: when a TX Message is invalid
+	 *   due to ECC errors or TXMAB underflow
+	 *   in this situatioon the system will transition to
+	 *   Restricted or Listen Only mode
+	 */
+
+	priv->can_err_id |= CAN_ERR_CRTL;
+	priv->can_err_data[1] |= CAN_ERR_CRTL_UNSPEC;
+
+	/* a mode change + invalid message would indicate
+	 * TX MAB Underflow
+	 */
+	if ((priv->status.intf & CAN_INT_MODIF) &&
+	    (priv->status.intf & CAN_INT_IVMIF)) {
+		return mcp25xxfd_can_ist_handle_serrif_txmab(spi);
+	}
+
+	/* for RX there is only the RXIF an indicator
+	 * - surprizingly RX-MAB does not change mode or anything
+	 */
+	if (priv->status.intf & CAN_INT_RXIF)
+		return mcp25xxfd_can_ist_handle_serrif_rxmab(spi);
+
+	/* the final case */
+	dev_warn_ratelimited(&spi->dev,
+			     "unidentified system error - intf =  %08x\n",
+			     priv->status.intf);
+
+	return 0;
+}
+
+static int mcp25xxfd_can_ist_handle_ivmif(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* if we have a systemerror as well, then ignore it */
+	if (priv->status.intf & CAN_INT_SERRIF)
+		return 0;
+
+	/* otherwise it is an RX issue, so account for it here */
+	priv->can_err_id |= CAN_ERR_PROT;
+	priv->can_err_data[2] |= CAN_ERR_PROT_FORM;
+	priv->net->stats.rx_frame_errors++;
+	priv->net->stats.rx_errors++;
+
+	return 0;
+}
+
+static int mcp25xxfd_disable_interrupts(struct spi_device *spi,
+					u32 speed_hz)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	priv->status.intf = 0;
+	return mcp25xxfd_cmd_write(spi, CAN_INT, 0, speed_hz);
+}
+
+static int mcp25xxfd_enable_interrupts(struct spi_device *spi,
+				       u32 speed_hz)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	priv->status.intf = CAN_INT_TEFIE |
+		CAN_INT_RXIE |
+		CAN_INT_MODIE |
+		CAN_INT_SERRIE |
+		CAN_INT_IVMIE |
+		CAN_INT_CERRIE |
+		CAN_INT_RXOVIE |
+		CAN_INT_ECCIE;
+	return mcp25xxfd_cmd_write(spi, CAN_INT,
+				   priv->status.intf,
+				   speed_hz);
+}
+
+static int mcp25xxfd_hw_wake(struct spi_device *spi)
+{
+	return mcp25xxfd_start_clock(spi, MCP25XXFD_CLK_USER_CAN);
+}
+
+static void mcp25xxfd_hw_sleep(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* disable interrupts */
+	mcp25xxfd_disable_interrupts(spi, priv->spi_setup_speed_hz);
+
+	/* stop the clocks */
+	mcp25xxfd_stop_clock(spi, MCP25XXFD_CLK_USER_CAN);
+}
+
+static int mcp25xxfd_can_ist_handle_status(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	const u32 clear_irq = CAN_INT_TBCIF |
+		CAN_INT_MODIF |
+		CAN_INT_SERRIF |
+		CAN_INT_CERRIF |
+		CAN_INT_WAKIF |
+		CAN_INT_IVMIF;
+	int ret;
+
+	/* clear all the interrupts asap */
+	ret = mcp25xxfd_cmd_write_mask(spi, CAN_INT,
+				       priv->status.intf & (~clear_irq),
+				       clear_irq,
+				       priv->spi_speed_hz);
+	if (ret)
+		return ret;
+
+	/* interrupt clearing info */
+	priv->bdiag1_clear_value = 0;
+	priv->bdiag1_clear_mask = 0;
+	priv->can_err_id = 0;
+	memset(priv->can_err_data, 0, 8);
+
+	/* state changes */
+	priv->new_state = priv->can.state;
+
+	/* clear queued fifos */
+	mcp25xxfd_clear_queued_fifos(spi);
+
+	/* system error interrupt needs to get handled first
+	 * to get us out of restricted mode
+	 */
+	if (priv->status.intf & CAN_INT_SERRIF) {
+		priv->stats.int_serr_count++;
+		ret = mcp25xxfd_can_ist_handle_serrif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* mode change interrupt */
+	if (priv->status.intf & CAN_INT_MODIF) {
+		priv->stats.int_mod_count++;
+		ret = mcp25xxfd_can_ist_handle_modif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* handle the rx */
+	if (priv->status.intf & CAN_INT_RXIF) {
+		priv->stats.int_rx_count++;
+		ret = mcp25xxfd_can_ist_handle_rxif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* handle aborted TX FIFOs */
+	if (priv->status.txatif) {
+		priv->stats.int_txat_count++;
+		ret = mcp25xxfd_can_ist_handle_txatif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* handle the tef */
+	if (priv->status.intf & CAN_INT_TEFIF) {
+		priv->stats.int_tef_count++;
+		ret = mcp25xxfd_can_ist_handle_tefif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* process the queued fifos */
+	ret = mcp25xxfd_process_queued_fifos(spi);
+
+	/* handle error interrupt flags */
+	if (priv->status.rxovif) {
+		priv->stats.int_rxov_count++;
+		ret = mcp25xxfd_can_ist_handle_rxovif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* sram ECC error interrupt */
+	if (priv->status.intf & CAN_INT_ECCIF) {
+		priv->stats.int_ecc_count++;
+		ret = mcp25xxfd_can_ist_handle_eccif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* message format interrupt */
+	if (priv->status.intf & CAN_INT_IVMIF) {
+		priv->stats.int_ivm_count++;
+		ret = mcp25xxfd_can_ist_handle_ivmif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* handle bus errors in more detail */
+	if (priv->status.intf & CAN_INT_CERRIF) {
+		priv->stats.int_cerr_count++;
+		ret = mcp25xxfd_can_ist_handle_cerrif(spi);
+		if (ret)
+			return ret;
+	}
+
+	/* Error counter handling */
+	if (priv->status.trec & CAN_TREC_TXWARN) {
+		priv->new_state = CAN_STATE_ERROR_WARNING;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_TX_WARNING;
+	}
+	if (priv->status.trec & CAN_TREC_RXWARN) {
+		priv->new_state = CAN_STATE_ERROR_WARNING;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_RX_WARNING;
+	}
+	if (priv->status.trec & CAN_TREC_TXBP) {
+		priv->new_state = CAN_STATE_ERROR_PASSIVE;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_TX_PASSIVE;
+	}
+	if (priv->status.trec & CAN_TREC_RXBP) {
+		priv->new_state = CAN_STATE_ERROR_PASSIVE;
+		priv->can_err_id |= CAN_ERR_CRTL;
+		priv->can_err_data[1] |= CAN_ERR_CRTL_RX_PASSIVE;
+	}
+	if (priv->status.trec & CAN_TREC_TXBO) {
+		priv->new_state = CAN_STATE_BUS_OFF;
+		priv->can_err_id |= CAN_ERR_BUSOFF;
+	}
+
+	/* based on the last state state check the new state */
+	switch (priv->can.state) {
+	case CAN_STATE_ERROR_ACTIVE:
+		if (priv->new_state >= CAN_STATE_ERROR_WARNING &&
+		    priv->new_state <= CAN_STATE_BUS_OFF)
+			priv->can.can_stats.error_warning++;
+		/* fallthrough */
+	case CAN_STATE_ERROR_WARNING:
+		if (priv->new_state >= CAN_STATE_ERROR_PASSIVE &&
+		    priv->new_state <= CAN_STATE_BUS_OFF)
+			priv->can.can_stats.error_passive++;
+		break;
+	default:
+		break;
+	}
+	priv->can.state = priv->new_state;
+
+	/* and send error packet */
+	if (priv->can_err_id)
+		mcp25xxfd_error_skb(priv->net);
+
+	/* handle BUS OFF */
+	if (priv->can.state == CAN_STATE_BUS_OFF) {
+		if (priv->can.restart_ms == 0) {
+			mcp25xxfd_stop_queue(priv->net);
+			priv->force_quit = 1;
+			priv->can.can_stats.bus_off++;
+			can_bus_off(priv->net);
+			mcp25xxfd_hw_sleep(spi);
+		}
+	} else {
+		/* restart the tx queue if needed */
+		if (priv->fifos.tx_processed_mask == priv->fifos.tx_fifo_mask)
+			mcp25xxfd_wake_queue(spi);
+	}
+
+	/* clear bdiag flags */
+	if (priv->bdiag1_clear_mask) {
+		ret = mcp25xxfd_cmd_write_mask(spi,
+					       CAN_BDIAG1,
+					       priv->bdiag1_clear_value,
+					       priv->bdiag1_clear_mask,
+					       priv->spi_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static irqreturn_t mcp25xxfd_can_ist(int irq, void *dev_id)
+{
+	struct mcp25xxfd_priv *priv = dev_id;
+	struct spi_device *spi = priv->spi;
+	int ret;
+
+	priv->stats.irq_calls++;
+	priv->stats.irq_state = IRQ_STATE_RUNNING;
+
+	while (!priv->force_quit) {
+		/* count irq loops */
+		priv->stats.irq_loops++;
+
+		/* copy pending to in_irq - any
+		 * updates that happen asyncronously
+		 * are not taken into account here
+		 */
+		priv->fifos.tx_pending_mask_in_irq =
+			priv->fifos.tx_pending_mask;
+
+		/* read interrupt status flags */
+		ret = mcp25xxfd_cmd_readn(spi, CAN_INT,
+					  &priv->status,
+					  sizeof(priv->status),
+					  priv->spi_speed_hz);
+		if (ret)
+			return ret;
+
+		/* only act if the mask is applied */
+		if ((priv->status.intf &
+		     (priv->status.intf >> CAN_INT_IE_SHIFT)) == 0)
+			break;
+
+		/* handle the status */
+		ret = mcp25xxfd_can_ist_handle_status(spi);
+		if (ret)
+			return ret;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int mcp25xxfd_get_berr_counter(const struct net_device *net,
+				      struct can_berr_counter *bec)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+
+	bec->txerr = (priv->status.trec & CAN_TREC_TEC_MASK) >>
+		CAN_TREC_TEC_SHIFT;
+	bec->rxerr = (priv->status.trec & CAN_TREC_REC_MASK) >>
+		CAN_TREC_REC_SHIFT;
+
+	return 0;
+}
+
+static int mcp25xxfd_power_enable(struct regulator *reg, int enable)
+{
+	if (IS_ERR_OR_NULL(reg))
+		return 0;
+
+	if (enable)
+		return regulator_enable(reg);
+	else
+		return regulator_disable(reg);
+}
+
+static int mcp25xxfd_do_set_mode(struct net_device *net, enum can_mode mode)
+{
+	switch (mode) {
+	case CAN_MODE_START:
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int mcp25xxfd_do_set_nominal_bittiming(struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	struct can_bittiming *bt = &priv->can.bittiming;
+	struct spi_device *spi = priv->spi;
+
+	int sjw = bt->sjw;
+	int pseg2 = bt->phase_seg2;
+	int pseg1 = bt->phase_seg1;
+	int propseg = bt->prop_seg;
+	int brp = bt->brp;
+
+	int tseg1 = propseg + pseg1;
+	int tseg2 = pseg2;
+
+	/* calculate nominal bit timing */
+	priv->regs.nbtcfg = ((sjw - 1) << CAN_NBTCFG_SJW_SHIFT) |
+		((tseg2 - 1) << CAN_NBTCFG_TSEG2_SHIFT) |
+		((tseg1 - 1) << CAN_NBTCFG_TSEG1_SHIFT) |
+		((brp - 1) << CAN_NBTCFG_BRP_SHIFT);
+
+	return mcp25xxfd_cmd_write(spi, CAN_NBTCFG,
+				   priv->regs.nbtcfg,
+				   priv->spi_setup_speed_hz);
+}
+
+static int mcp25xxfd_do_set_data_bittiming(struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	struct can_bittiming *bt = &priv->can.data_bittiming;
+	struct spi_device *spi = priv->spi;
+
+	int sjw = bt->sjw;
+	int pseg2 = bt->phase_seg2;
+	int pseg1 = bt->phase_seg1;
+	int propseg = bt->prop_seg;
+	int brp = bt->brp;
+
+	int tseg1 = propseg + pseg1;
+	int tseg2 = pseg2;
+
+	int ret;
+
+	/* set up Transmitter delay compensation */
+	if (!priv->regs.tdc)
+		priv->regs.tdc = CAN_TDC_EDGFLTEN |
+			(CAN_TDC_TDCMOD_AUTO << CAN_TDC_TDCMOD_SHIFT);
+	ret = mcp25xxfd_cmd_write(spi, CAN_TDC, priv->regs.tdc,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* calculate nominal bit timing */
+	priv->regs.dbtcfg = ((sjw - 1) << CAN_DBTCFG_SJW_SHIFT) |
+		((tseg2 - 1) << CAN_DBTCFG_TSEG2_SHIFT) |
+		((tseg1 - 1) << CAN_DBTCFG_TSEG1_SHIFT) |
+		((brp - 1) << CAN_DBTCFG_BRP_SHIFT);
+
+	return mcp25xxfd_cmd_write(spi, CAN_DBTCFG,
+				   priv->regs.dbtcfg,
+				   priv->spi_setup_speed_hz);
+}
+
+static int mcp25xxfd_hw_probe(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int ret;
+
+	/* Wait for oscillator startup timer after power up */
+	mdelay(MCP25XXFD_OST_DELAY_MS);
+
+	/* send a "blind" reset, hoping we are in Config mode */
+	mcp25xxfd_cmd_reset(spi, priv->spi_setup_speed_hz);
+
+	/* Wait for oscillator startup again */
+	mdelay(MCP25XXFD_OST_DELAY_MS);
+
+	/* check clock register that the clock is ready or disabled */
+	ret = mcp25xxfd_cmd_read(spi, MCP25XXFD_OSC,
+				 &priv->regs.osc,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* there can only be one... */
+	switch (priv->regs.osc &
+		(MCP25XXFD_OSC_OSCRDY | MCP25XXFD_OSC_OSCDIS)) {
+	case MCP25XXFD_OSC_OSCRDY: /* either the clock is ready */
+		break;
+	case MCP25XXFD_OSC_OSCDIS: /* or the clock is disabled */
+		/* wakeup sleeping system */
+		ret = mcp25xxfd_wake_from_sleep(spi);
+		if (ret)
+			return ret;
+		/* send a reset, hoping we are now in Config mode */
+		mcp25xxfd_cmd_reset(spi, priv->spi_setup_speed_hz);
+
+		/* Wait for oscillator startup again */
+		mdelay(MCP25XXFD_OST_DELAY_MS);
+		break;
+	default:
+		/* otherwise there is no valid device (or in strange state)
+		 *
+		 * if PLL is enabled but not ready, then there may be
+		 * something "fishy"
+		 * this happened during driver development
+		 * (enabling pll, when when on wrong clock), so best warn
+		 * about such a possibility
+		 */
+		if ((priv->regs.osc &
+		     (MCP25XXFD_OSC_PLLEN | MCP25XXFD_OSC_PLLRDY))
+		    == MCP25XXFD_OSC_PLLEN)
+			dev_err(&spi->dev,
+				"mcp25xxfd may be in a strange state - a power disconnect may be required\n");
+
+		return -ENODEV;
+	}
+
+	/* check if we are in config mode already*/
+
+	/* read CON register and match */
+	ret = mcp25xxfd_cmd_read(spi, CAN_CON,
+				 &priv->regs.con,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* apply mask and check */
+	if ((priv->regs.con & CAN_CON_DEFAULT_MASK) == CAN_CON_DEFAULT) {
+		priv->active_can_mode = CAN_CON_MODE_CONFIG;
+		return 0;
+	}
+
+	/* as per datasheet a reset only works in Config Mode
+	 * so as we have in principle no knowledge of the current
+	 * mode that the controller is in we have no safe way
+	 * to detect the device correctly
+	 * hence we need to "blindly" put the controller into
+	 * config mode.
+	 * on the "save" side, the OSC reg has to be valid already,
+	 * so there is a chance we got the controller...
+	 */
+
+	/* blindly force it into config mode */
+	priv->regs.con = CAN_CON_DEFAULT;
+	priv->active_can_mode = CAN_CON_MODE_CONFIG;
+	ret = mcp25xxfd_cmd_write(spi, CAN_CON, CAN_CON_DEFAULT,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* delay some time */
+	mdelay(MCP25XXFD_OST_DELAY_MS);
+
+	/* reset can controller */
+	mcp25xxfd_cmd_reset(spi, priv->spi_setup_speed_hz);
+
+	/* delay some time */
+	mdelay(MCP25XXFD_OST_DELAY_MS);
+
+	/* read CON register and match a final time */
+	ret = mcp25xxfd_cmd_read(spi, CAN_CON,
+				 &priv->regs.con,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* apply mask and check */
+	if ((priv->regs.con & CAN_CON_DEFAULT_MASK) != CAN_CON_DEFAULT)
+		return -ENODEV;
+
+	/* just in case: disable interrupts on controller */
+	return mcp25xxfd_disable_interrupts(spi,
+					    priv->spi_setup_speed_hz);
+}
+
+static int mcp25xxfd_setup_osc(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	int val = ((priv->config.clock_pll) ? MCP25XXFD_OSC_PLLEN : 0)
+		| ((priv->config.clock_div2) ? MCP25XXFD_OSC_SCLKDIV : 0);
+	int waitfor = ((priv->config.clock_pll) ? MCP25XXFD_OSC_PLLRDY : 0)
+		| ((priv->config.clock_div2) ? MCP25XXFD_OSC_SCLKRDY : 0)
+		| MCP25XXFD_OSC_OSCRDY;
+	int ret;
+	unsigned long timeout;
+
+	/* manage clock_out divider */
+	switch (priv->config.clock_odiv) {
+	case 10:
+		val |= (MCP25XXFD_OSC_CLKODIV_10)
+			<< MCP25XXFD_OSC_CLKODIV_SHIFT;
+		break;
+	case 4:
+		val |= (MCP25XXFD_OSC_CLKODIV_4)
+			<< MCP25XXFD_OSC_CLKODIV_SHIFT;
+		break;
+	case 2:
+		val |= (MCP25XXFD_OSC_CLKODIV_2)
+			<< MCP25XXFD_OSC_CLKODIV_SHIFT;
+		break;
+	case 1:
+		val |= (MCP25XXFD_OSC_CLKODIV_1)
+			<< MCP25XXFD_OSC_CLKODIV_SHIFT;
+		break;
+	case 0:
+		/* this means implicitly SOF output */
+		val |= (MCP25XXFD_OSC_CLKODIV_10)
+			<< MCP25XXFD_OSC_CLKODIV_SHIFT;
+		break;
+	default:
+		dev_err(&spi->dev,
+			"Unsupported output clock divider %i\n",
+			priv->config.clock_odiv);
+		return -EINVAL;
+	}
+
+	/* write clock */
+	ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_OSC, val,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* wait for synced pll/osc/sclk */
+	timeout = jiffies + MCP25XXFD_OSC_POLLING_JIFFIES;
+	while (time_before_eq(jiffies, timeout)) {
+		ret = mcp25xxfd_cmd_read(spi, MCP25XXFD_OSC,
+					 &priv->regs.osc,
+					 priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		if ((priv->regs.osc & waitfor) == waitfor)
+			return 0;
+	}
+
+	dev_err(&spi->dev,
+		"Clock did not lock within the timeout period\n");
+
+	/* we timed out */
+	return -ENODEV;
+}
+
+static int mcp25xxfd_setup_fifo(struct net_device *net,
+				struct mcp25xxfd_priv *priv,
+				struct spi_device *spi)
+{
+	u32 val, available_memory, tx_memory_used;
+	int ret;
+	int i, fifo;
+
+	/* clear all filter */
+	for (i = 0; i < 32; i++) {
+		ret = mcp25xxfd_cmd_write(spi, CAN_FLTOBJ(i), 0,
+					  priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		ret = mcp25xxfd_cmd_write(spi, CAN_FLTMASK(i), 0,
+					  priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		ret = mcp25xxfd_cmd_write_mask(spi, CAN_FLTCON(i), 0,
+					       CAN_FILCON_MASK(i),
+					       priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+	}
+
+	/* decide on TEF, tx and rx FIFOS */
+	switch (net->mtu) {
+	case CAN_MTU:
+		/* note: if we have INT1 connected to a GPIO
+		 * then we could handle this differently and more
+		 * efficiently
+		 */
+
+		/* mtu is 8 */
+		priv->fifos.payload_size = 8;
+		priv->fifos.payload_mode = CAN_TXQCON_PLSIZE_8;
+
+		/* 7 tx fifos starting at fifo 1 */
+		priv->fifos.tx_fifos = 7;
+
+		/* 24 rx fifos with 1 buffers/fifo */
+		priv->fifos.rx_fifo_depth = 1;
+
+		break;
+	case CANFD_MTU:
+		/* wish there was a way to have hw filters
+		 * that can separate based on length ...
+		 */
+		/* MTU is 64 */
+		priv->fifos.payload_size = 64;
+		priv->fifos.payload_mode = CAN_TXQCON_PLSIZE_64;
+
+		/* 7 tx fifos */
+		priv->fifos.tx_fifos = 7;
+
+		/* 19 rx fifos with 1 buffer/fifo */
+		priv->fifos.rx_fifo_depth = 1;
+
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* if defined as a module modify the number of tx_fifos */
+	if (tx_fifos) {
+		dev_info(&spi->dev,
+			 "Using %i tx-fifos as per module parameter\n",
+			 tx_fifos);
+		priv->fifos.tx_fifos = tx_fifos;
+	}
+
+	/* check range - we need 1 RX-fifo and one tef-fifo, hence 30 */
+	if (priv->fifos.tx_fifos > 30) {
+		dev_err(&spi->dev,
+			"There is an absolute maximum of 30 tx-fifos\n");
+		return -EINVAL;
+	}
+
+	tx_memory_used = priv->fifos.tx_fifos *
+		(sizeof(struct mcp25xxfd_obj_tef) +
+		 sizeof(struct mcp25xxfd_obj_tx) +
+		 priv->fifos.payload_size);
+	/* check that we are not exceeding memory limits with 1 RX buffer */
+	if (tx_memory_used + (sizeof(struct mcp25xxfd_obj_rx) +
+		   priv->fifos.payload_size) > MCP25XXFD_BUFFER_TXRX_SIZE) {
+		dev_err(&spi->dev,
+			"Configured %i tx-fifos exceeds available memory already\n",
+			priv->fifos.tx_fifos);
+		return -EINVAL;
+	}
+
+	/* calculate possible amount of RX fifos */
+	available_memory = MCP25XXFD_BUFFER_TXRX_SIZE - tx_memory_used;
+
+	priv->fifos.rx_fifos = available_memory /
+		(sizeof(struct mcp25xxfd_obj_rx) +
+		 priv->fifos.payload_size) /
+		priv->fifos.rx_fifo_depth;
+
+	/* we only support 31 FIFOS in total (TEF = FIFO0),
+	 * so modify rx accordingly
+	 */
+	if (priv->fifos.tx_fifos + priv->fifos.rx_fifos > 31)
+		priv->fifos.rx_fifos = 31 - priv->fifos.tx_fifos;
+
+	/* calculate effective memory used */
+	available_memory -= priv->fifos.rx_fifos *
+		(sizeof(struct mcp25xxfd_obj_rx) +
+		 priv->fifos.payload_size) *
+		priv->fifos.rx_fifo_depth;
+
+	/* calcluate tef size */
+	priv->fifos.tef_fifos = priv->fifos.tx_fifos;
+	fifo = available_memory / sizeof(struct mcp25xxfd_obj_tef);
+	if (fifo > 0) {
+		priv->fifos.tef_fifos += fifo;
+		if (priv->fifos.tef_fifos > 32)
+			priv->fifos.tef_fifos = 32;
+	}
+
+	/* calculate rx/tx fifo start */
+	priv->fifos.rx_fifo_start = 1;
+	priv->fifos.tx_fifo_start =
+		priv->fifos.rx_fifo_start + priv->fifos.rx_fifos;
+
+	/* set up TEF SIZE to the number of tx_fifos and IRQ */
+	priv->regs.tefcon = CAN_TEFCON_FRESET |
+		CAN_TEFCON_TEFNEIE |
+		CAN_TEFCON_TEFTSEN |
+		((priv->fifos.tef_fifos - 1) << CAN_TEFCON_FSIZE_SHIFT);
+
+	ret = mcp25xxfd_cmd_write(spi, CAN_TEFCON,
+				  priv->regs.tefcon,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* set up tx fifos */
+	val = CAN_FIFOCON_TXEN |
+		CAN_FIFOCON_TXATIE | /* show up txatie flags in txatif reg */
+		CAN_FIFOCON_FRESET | /* reset FIFO */
+		(priv->fifos.payload_mode << CAN_FIFOCON_PLSIZE_SHIFT) |
+		(0 << CAN_FIFOCON_FSIZE_SHIFT); /* 1 FIFO only */
+
+	if (priv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+		if (three_shot)
+			val |= CAN_FIFOCON_TXAT_THREE_SHOT <<
+				CAN_FIFOCON_TXAT_SHIFT;
+		else
+			val |= CAN_FIFOCON_TXAT_ONE_SHOT <<
+				CAN_FIFOCON_TXAT_SHIFT;
+	else
+		val |= CAN_FIFOCON_TXAT_UNLIMITED <<
+			CAN_FIFOCON_TXAT_SHIFT;
+
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		fifo = priv->fifos.tx_fifo_start + i;
+		ret = mcp25xxfd_cmd_write(spi, CAN_FIFOCON(fifo),
+					  /* the prioriy needs to be inverted
+					   * we need to run from lowest to
+					   * highest to avoid MAB errors
+					   */
+					  val | ((31 - fifo) <<
+						 CAN_FIFOCON_TXPRI_SHIFT),
+					  priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		priv->fifos.tx_fifo_mask |= BIT(fifo);
+	}
+
+	/* now set up RX FIFO */
+	for (i = 0,
+	     fifo = priv->fifos.rx_fifo_start + priv->fifos.rx_fifos - 1;
+	     i < priv->fifos.rx_fifos; i++, fifo--) {
+		/* prepare the fifo itself */
+		ret = mcp25xxfd_cmd_write(spi, CAN_FIFOCON(fifo),
+					  (priv->fifos.payload_mode <<
+					   CAN_FIFOCON_PLSIZE_SHIFT) |
+					  ((priv->fifos.rx_fifo_depth - 1) <<
+					   CAN_FIFOCON_FSIZE_SHIFT) |
+					  /* RX timestamps: */
+					  CAN_FIFOCON_RXTSEN |
+					  /* reset FIFO: */
+					  CAN_FIFOCON_FRESET |
+					  /* FIFO Full: */
+					  CAN_FIFOCON_TFERFFIE |
+					  /* FIFO Half Full: */
+					  CAN_FIFOCON_TFHRFHIE |
+					  /* FIFO not empty: */
+					  CAN_FIFOCON_TFNRFNIE |
+					  /* on last fifo add overflow flag: */
+					  ((i == priv->fifos.rx_fifos - 1) ?
+					   CAN_FIFOCON_RXOVIE : 0),
+					  priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		/* prepare the rx filter config: filter i directs to fifo
+		 * FLTMSK and FLTOBJ are 0 already, so they match everything
+		 */
+		ret = mcp25xxfd_cmd_write_mask(spi, CAN_FLTCON(i),
+					       CAN_FIFOCON_FLTEN(i) |
+					       (fifo << CAN_FILCON_SHIFT(i)),
+					       CAN_FIFOCON_FLTEN(i) |
+					       CAN_FILCON_MASK(i),
+					       priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+
+		priv->fifos.rx_fifo_mask |= BIT(fifo);
+	}
+
+	/* we need to move out of CONFIG mode shortly to get the addresses */
+	ret = mcp25xxfd_set_opmode(spi, CAN_CON_MODE_INTERNAL_LOOPBACK,
+				   priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* for the TEF fifo */
+	ret = mcp25xxfd_cmd_read(spi, CAN_TEFUA, &val,
+				 priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+	priv->fifos.tef_address = val;
+	priv->fifos.tef_address_start = val;
+	priv->fifos.tef_address_end = priv->fifos.tef_address_start +
+		priv->fifos.tef_fifos * sizeof(struct mcp25xxfd_obj_tef) -
+		1;
+
+	/* get all the relevant addresses for the transmit fifos */
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		fifo = priv->fifos.tx_fifo_start + i;
+		ret = mcp25xxfd_cmd_read(spi, CAN_FIFOUA(fifo),
+					 &val, priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+		priv->fifos.fifo_address[fifo] = val;
+	}
+
+	/* and prepare the spi_messages */
+	ret = mcp25xxfd_fill_spi_transmit_fifos(priv);
+	if (ret)
+		return ret;
+
+	/* get all the relevant addresses for the rx fifos */
+	for (i = 0; i < priv->fifos.rx_fifos; i++) {
+		fifo = priv->fifos.rx_fifo_start + i;
+		ret = mcp25xxfd_cmd_read(spi, CAN_FIFOUA(fifo),
+					 &val, priv->spi_setup_speed_hz);
+		if (ret)
+			return ret;
+	       priv->fifos.fifo_address[fifo] = val;
+	}
+
+	/* now get back into config mode */
+	ret = mcp25xxfd_set_opmode(spi, CAN_CON_MODE_CONFIG,
+				   priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int mcp25xxfd_setup(struct net_device *net,
+			   struct mcp25xxfd_priv *priv,
+			   struct spi_device *spi)
+{
+	int ret;
+
+	/* set up pll/clock if required */
+	ret = mcp25xxfd_setup_osc(spi);
+	if (ret)
+		return ret;
+
+	/* set up RAM ECC */
+	priv->regs.ecccon = MCP25XXFD_ECCCON_ECCEN |
+		MCP25XXFD_ECCCON_SECIE |
+		MCP25XXFD_ECCCON_DEDIE;
+	ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_ECCCON,
+				  priv->regs.ecccon,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* clean SRAM now that we have ECC enabled
+	 * only this case it is clear that all RAM cels have
+	 * valid ECC bits
+	 */
+	ret = mcp25xxfd_clean_sram(spi, priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* time stamp control register - 1ns resolution, but disabled */
+	ret = mcp25xxfd_cmd_write(spi, CAN_TBC, 0,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+	priv->regs.tscon = CAN_TSCON_TBCEN |
+		((priv->can.clock.freq / 1000000)
+		 << CAN_TSCON_TBCPRE_SHIFT);
+	ret = mcp25xxfd_cmd_write(spi, CAN_TSCON,
+				  priv->regs.tscon,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* setup value of con_register */
+	priv->regs.con = CAN_CON_STEF /* enable TEF */;
+
+	/* transmission bandwidth sharing bits */
+	if (bw_sharing_log2bits > 12)
+		bw_sharing_log2bits = 12;
+	priv->regs.con |= bw_sharing_log2bits << CAN_CON_TXBWS_SHIFT;
+	/* non iso FD mode */
+	if (!(priv->can.ctrlmode & CAN_CTRLMODE_FD_NON_ISO))
+		priv->regs.con |= CAN_CON_ISOCRCEN;
+	/* one shot */
+	if (priv->can.ctrlmode & CAN_CTRLMODE_ONE_SHOT)
+		priv->regs.con |= CAN_CON_RTXAT;
+
+	/* and put us into default mode = CONFIG */
+	priv->regs.con |= (CAN_CON_MODE_CONFIG << CAN_CON_REQOP_SHIFT) |
+		(CAN_CON_MODE_CONFIG << CAN_CON_OPMOD_SHIFT);
+	/* apply it now - later we will only switch opsmodes... */
+	ret = mcp25xxfd_cmd_write(spi, CAN_CON,
+				  priv->regs.con,
+				  priv->spi_setup_speed_hz);
+
+	/* setup fifos - this also puts the system into sleep mode */
+	return mcp25xxfd_setup_fifo(net, priv, spi);
+}
+
+static int mcp25xxfd_open(struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	struct spi_device *spi = priv->spi;
+	int ret;
+
+	ret = open_candev(net);
+	if (ret) {
+		dev_err(&spi->dev, "unable to set initial baudrate!\n");
+		return ret;
+	}
+
+	mcp25xxfd_power_enable(priv->transceiver, 1);
+
+	priv->force_quit = 0;
+
+	/* clear those statistics */
+	memset(&priv->stats, 0, sizeof(priv->stats));
+
+	ret = request_threaded_irq(spi->irq, NULL,
+				   mcp25xxfd_can_ist,
+				   IRQF_ONESHOT | IRQF_TRIGGER_LOW,
+				   DEVICE_NAME, priv);
+	if (ret) {
+		dev_err(&spi->dev, "failed to acquire irq %d - %i\n",
+			spi->irq, ret);
+		mcp25xxfd_power_enable(priv->transceiver, 0);
+		close_candev(net);
+		return ret;
+	}
+
+	/* wake from sleep if necessary */
+	ret = mcp25xxfd_hw_wake(spi);
+	if (ret)
+		goto open_clean;
+
+	ret = mcp25xxfd_setup(net, priv, spi);
+	if (ret)
+		goto open_clean;
+
+	mcp25xxfd_do_set_nominal_bittiming(net);
+	mcp25xxfd_do_set_data_bittiming(net);
+
+	ret = mcp25xxfd_set_normal_opmode(spi);
+	if (ret)
+		goto open_clean;
+	/* setting up default state */
+	priv->can.state = CAN_STATE_ERROR_ACTIVE;
+
+	/* only now enable the interrupt on the controller */
+	ret =  mcp25xxfd_enable_interrupts(spi,
+					   priv->spi_setup_speed_hz);
+	if (ret)
+		goto open_clean;
+
+	can_led_event(net, CAN_LED_EVENT_OPEN);
+
+	priv->tx_queue_status = TX_QUEUE_STATUS_RUNNING;
+	netif_wake_queue(net);
+
+	return 0;
+
+open_clean:
+	mcp25xxfd_disable_interrupts(spi, priv->spi_setup_speed_hz);
+	free_irq(spi->irq, priv);
+	mcp25xxfd_hw_sleep(spi);
+	mcp25xxfd_power_enable(priv->transceiver, 0);
+	close_candev(net);
+
+	return ret;
+}
+
+static void mcp25xxfd_clean(struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	int i;
+
+	for (i = 0; i < priv->fifos.tx_fifos; i++) {
+		if (priv->fifos.tx_pending_mask & BIT(i)) {
+			can_free_echo_skb(priv->net, 0);
+			priv->net->stats.tx_errors++;
+		}
+	}
+
+	priv->fifos.tx_pending_mask = 0;
+}
+
+static int mcp25xxfd_stop(struct net_device *net)
+{
+	struct mcp25xxfd_priv *priv = netdev_priv(net);
+	struct spi_device *spi = priv->spi;
+
+	close_candev(net);
+
+	kfree(priv->spi_transmit_fifos);
+	priv->spi_transmit_fifos = NULL;
+
+	priv->force_quit = 1;
+	free_irq(spi->irq, priv);
+
+	/* Disable and clear pending interrupts */
+	mcp25xxfd_disable_interrupts(spi, priv->spi_setup_speed_hz);
+
+	mcp25xxfd_clean(net);
+
+	mcp25xxfd_hw_sleep(spi);
+
+	mcp25xxfd_power_enable(priv->transceiver, 0);
+
+	priv->can.state = CAN_STATE_STOPPED;
+
+	can_led_event(net, CAN_LED_EVENT_STOP);
+
+	return 0;
+}
+
+static const struct net_device_ops mcp25xxfd_netdev_ops = {
+	.ndo_open = mcp25xxfd_open,
+	.ndo_stop = mcp25xxfd_stop,
+	.ndo_start_xmit = mcp25xxfd_start_xmit,
+	.ndo_change_mtu = can_change_mtu,
+};
+
+static const struct of_device_id mcp25xxfd_of_match[] = {
+	{
+		.compatible	= "microchip,mcp2517fd",
+		.data		= (void *)CAN_MCP2517FD,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, mcp25xxfd_of_match);
+
+static const struct spi_device_id mcp25xxfd_id_table[] = {
+	{
+		.name		= "mcp2517fd",
+		.driver_data	= (kernel_ulong_t)CAN_MCP2517FD,
+	},
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, mcp25xxfd_id_table);
+
+static int mcp25xxfd_dump_regs(struct seq_file *file, void *offset)
+{
+	struct spi_device *spi = file->private;
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	u32 data[CAN_TXQUA - CAN_CON + 4];
+	int i;
+	int count;
+	int ret;
+
+	count = (CAN_TXQUA - CAN_CON) / 4 + 1;
+	ret = mcp25xxfd_cmd_readn(spi, CAN_CON, data, 4 * count,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	mcp25xxfd_convert_to_cpu((u32 *)data, 4 * count);
+
+	for (i = 0; i < count; i++) {
+		seq_printf(file, "Reg 0x%03x = 0x%08x\n",
+			   CAN_CON + 4 * i,
+			   ((u32 *)data)[i]);
+	}
+
+	count = (MCP25XXFD_ECCSTAT - MCP25XXFD_OSC) / 4 + 1;
+	ret = mcp25xxfd_cmd_readn(spi, MCP25XXFD_OSC, data, 4 * count,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+	mcp25xxfd_convert_to_cpu((u32 *)data, 4 * count);
+
+	for (i = 0; i < count; i++) {
+		seq_printf(file, "Reg 0x%03x = 0x%08x\n",
+			   MCP25XXFD_OSC + 4 * i,
+			   ((u32 *)data)[i]);
+	}
+
+	return 0;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+static void mcp25xxfd_debugfs_add(struct mcp25xxfd_priv *priv)
+{
+	struct dentry *root, *fifousage, *fifoaddr, *rx, *tx, *status,
+		*regs, *stats, *rxdlc, *txdlc;
+	char name[32];
+	int i;
+
+	/* create the net device name */
+	snprintf(name, sizeof(name), DEVICE_NAME "-%s", priv->net->name);
+	priv->debugfs_dir = debugfs_create_dir(name, NULL);
+	root = priv->debugfs_dir;
+
+	rx = debugfs_create_dir("rx", root);
+	tx = debugfs_create_dir("tx", root);
+	fifoaddr = debugfs_create_dir("fifo_address", root);
+	status = debugfs_create_dir("status", root);
+	regs = debugfs_create_dir("regs", root);
+	stats = debugfs_create_dir("stats", root);
+	fifousage = debugfs_create_dir("fifo_usage", stats);
+	rxdlc = debugfs_create_dir("rx_dlc_usage", stats);
+	txdlc = debugfs_create_dir("tx_dlc_usage", stats);
+
+	/* add spi speed info */
+	debugfs_create_u32("spi_setup_speed_hz", 0444, root,
+			   &priv->spi_setup_speed_hz);
+	debugfs_create_u32("spi_speed_hz", 0444, root,
+			   &priv->spi_speed_hz);
+
+	/* add irq state info */
+	debugfs_create_u32("irq_state", 0444, root, &priv->stats.irq_state);
+
+	/* for the clock user mask */
+	debugfs_create_u32("clk_user_mask", 0444, root, &priv->clk_user_mask);
+
+	/* add fd statistics */
+	debugfs_create_u64("rx_fd_frames", 0444, stats,
+			   &priv->stats.rx_fd_count);
+	debugfs_create_u64("tx_fd_frames", 0444, stats,
+			   &priv->stats.tx_fd_count);
+	debugfs_create_u64("rx_brs_frames", 0444, stats,
+			   &priv->stats.rx_brs_count);
+	debugfs_create_u64("tx_brs_frames", 0444, stats,
+			   &priv->stats.tx_brs_count);
+
+	/* export the status structure */
+	debugfs_create_x32("intf", 0444, status, &priv->status.intf);
+	debugfs_create_x32("rx_if", 0444, status, &priv->status.rxif);
+	debugfs_create_x32("tx_if", 0444, status, &priv->status.txif);
+	debugfs_create_x32("rx_ovif", 0444, status, &priv->status.rxovif);
+	debugfs_create_x32("tx_atif", 0444, status, &priv->status.txatif);
+	debugfs_create_x32("tx_req", 0444, status, &priv->status.txreq);
+	debugfs_create_x32("trec", 0444, status, &priv->status.trec);
+	debugfs_create_x32("bdiag0", 0444, status, &priv->status.bdiag0);
+	debugfs_create_x32("bdiag1", 0444, status, &priv->status.bdiag1);
+
+	/* some configuration registers */
+	debugfs_create_x32("con", 0444, regs, &priv->regs.con);
+	debugfs_create_x32("ecccon", 0444, regs, &priv->regs.ecccon);
+	debugfs_create_x32("osc", 0444, regs, &priv->regs.osc);
+	debugfs_create_x32("iocon", 0444, regs, &priv->regs.iocon);
+	debugfs_create_x32("tdc", 0774, regs, &priv->regs.tdc);
+	debugfs_create_x32("tscon", 0444, regs, &priv->regs.tscon);
+	debugfs_create_x32("nbtcfg", 0444, regs, &priv->regs.nbtcfg);
+	debugfs_create_x32("dbtcfg", 0444, regs, &priv->regs.dbtcfg);
+
+	/* information on fifos */
+	debugfs_create_u32("fifo_start", 0444, rx,
+			   &priv->fifos.rx_fifo_start);
+	debugfs_create_u32("fifo_count", 0444, rx,
+			   &priv->fifos.rx_fifos);
+	debugfs_create_x32("fifo_mask", 0444, rx,
+			   &priv->fifos.rx_fifo_mask);
+	debugfs_create_u64("rx_overflow", 0444, rx,
+			   &priv->stats.rx_overflow);
+	debugfs_create_u64("rx_mab", 0444, stats,
+			   &priv->stats.rx_mab);
+
+	debugfs_create_u32("fifo_start", 0444, tx,
+			   &priv->fifos.tx_fifo_start);
+	debugfs_create_u32("fifo_count", 0444, tx,
+			   &priv->fifos.tx_fifos);
+	debugfs_create_x32("fifo_mask", 0444, tx,
+			   &priv->fifos.tx_fifo_mask);
+	debugfs_create_x32("fifo_pending", 0444, tx,
+			   &priv->fifos.tx_pending_mask);
+	debugfs_create_x32("fifo_submitted", 0444, tx,
+			   &priv->fifos.tx_submitted_mask);
+	debugfs_create_x32("fifo_processed", 0444, tx,
+			   &priv->fifos.tx_processed_mask);
+	debugfs_create_u32("queue_status", 0444, tx,
+			   &priv->tx_queue_status);
+	debugfs_create_u64("tx_mab", 0444, stats,
+			   &priv->stats.tx_mab);
+
+	debugfs_create_u32("tef_count", 0444, tx,
+			   &priv->fifos.tef_fifos);
+
+	debugfs_create_u32("fifo_max_payload_size", 0444, root,
+			   &priv->fifos.payload_size);
+
+	/* interrupt statistics */
+	debugfs_create_u64("int", 0444, stats,
+			   &priv->stats.irq_calls);
+	debugfs_create_u64("int_loops", 0444, stats,
+			   &priv->stats.irq_loops);
+	debugfs_create_u64("int_ivm", 0444, stats,
+			   &priv->stats.int_ivm_count);
+	debugfs_create_u64("int_wake", 0444, stats,
+			   &priv->stats.int_wake_count);
+	debugfs_create_u64("int_cerr", 0444, stats,
+			   &priv->stats.int_cerr_count);
+	debugfs_create_u64("int_serr", 0444, stats,
+			   &priv->stats.int_serr_count);
+	debugfs_create_u64("int_rxov", 0444, stats,
+			   &priv->stats.int_rxov_count);
+	debugfs_create_u64("int_txat", 0444, stats,
+			   &priv->stats.int_txat_count);
+	debugfs_create_u64("int_spicrc", 0444, stats,
+			   &priv->stats.int_spicrc_count);
+	debugfs_create_u64("int_ecc", 0444, stats,
+			   &priv->stats.int_ecc_count);
+	debugfs_create_u64("int_tef", 0444, stats,
+			   &priv->stats.int_tef_count);
+	debugfs_create_u64("int_mod", 0444, stats,
+			   &priv->stats.int_mod_count);
+	debugfs_create_u64("int_tbc", 0444, stats,
+			   &priv->stats.int_tbc_count);
+	debugfs_create_u64("int_rx", 0444, stats,
+			   &priv->stats.int_rx_count);
+	debugfs_create_u64("int_tx", 0444, stats,
+			   &priv->stats.int_tx_count);
+
+	/* dlc statistics */
+	for (i = 0; i < 16; i++) {
+		snprintf(name, sizeof(name), "%02i", i);
+		debugfs_create_u64(name, 0444, rxdlc,
+				   &priv->stats.rx_dlc_usage[i]);
+		debugfs_create_u64(name, 0444, txdlc,
+				   &priv->stats.tx_dlc_usage[i]);
+	}
+
+	/* statistics on fifo buffer usage and address */
+	for (i = 1; i < 32; i++) {
+		snprintf(name, sizeof(name), "%02i", i);
+		debugfs_create_u64(name, 0444, fifousage,
+				   &priv->stats.fifo_usage[i]);
+		debugfs_create_u32(name, 0444, fifoaddr,
+				   &priv->fifos.fifo_address[i]);
+	}
+
+	/* dump the controller registers themselves */
+	debugfs_create_devm_seqfile(&priv->spi->dev, "reg_dump",
+				    root, mcp25xxfd_dump_regs);
+}
+
+static void mcp25xxfd_debugfs_remove(struct mcp25xxfd_priv *priv)
+{
+	debugfs_remove_recursive(priv->debugfs_dir);
+}
+
+#else
+static void mcp25xxfd_debugfs_add(struct mcp25xxfd_priv *priv)
+{
+	return 0;
+}
+
+static void mcp25xxfd_debugfs_remove(struct mcp25xxfd_priv *priv)
+{
+}
+#endif
+
+#ifdef CONFIG_OF_DYNAMIC
+int mcp25xxfd_of_parse(struct mcp25xxfd_priv *priv)
+{
+	struct spi_device *spi = priv->spi;
+	const struct device_node *np = spi->dev.of_node;
+	u32 val;
+	int ret;
+
+	priv->config.clock_div2 =
+		of_property_read_bool(np, "microchip,clock-div2");
+
+	ret = of_property_read_u32_index(np, "microchip,clock-out-div",
+					 0, &val);
+	if (!ret) {
+		switch (val) {
+		case 0:
+		case 1:
+		case 2:
+		case 4:
+		case 10:
+			priv->config.clock_odiv = val;
+			break;
+		default:
+			dev_err(&spi->dev,
+				"Invalid value in device tree for microchip,clock_out_div: %u - valid values: 0, 1, 2, 4, 10\n",
+				val);
+			return -EINVAL;
+		}
+	}
+
+	priv->config.gpio_opendrain =
+		of_property_read_bool(np, "microchip,gpio-open-drain");
+
+	return 0;
+}
+#else
+int mcp25xxfd_of_parse(struct mcp25xxfd_priv *priv)
+{
+	return 0;
+}
+#endif
+
+static int mcp25xxfd_can_probe(struct spi_device *spi)
+{
+	const struct of_device_id *of_id =
+		of_match_device(mcp25xxfd_of_match, &spi->dev);
+	struct net_device *net;
+	struct mcp25xxfd_priv *priv;
+	struct clk *clk;
+	int ret, freq;
+
+	/* as irq_create_fwspec_mapping() can return 0, check for it */
+	if (spi->irq <= 0) {
+		dev_err(&spi->dev, "no valid irq line defined: irq = %i\n",
+			spi->irq);
+		return -EINVAL;
+	}
+
+	clk = devm_clk_get(&spi->dev, NULL);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
+
+	freq = clk_get_rate(clk);
+	if (freq < MCP25XXFD_MIN_CLOCK_FREQUENCY ||
+	    freq > MCP25XXFD_MAX_CLOCK_FREQUENCY) {
+		dev_err(&spi->dev,
+			"Clock frequency %i is not in range [%i:%i]\n",
+			freq,
+			MCP25XXFD_MIN_CLOCK_FREQUENCY,
+			MCP25XXFD_MAX_CLOCK_FREQUENCY);
+		return -ERANGE;
+	}
+
+	/* Allocate can/net device */
+	net = alloc_candev(sizeof(*priv), TX_ECHO_SKB_MAX);
+	if (!net)
+		return -ENOMEM;
+
+	net->netdev_ops = &mcp25xxfd_netdev_ops;
+	net->flags |= IFF_ECHO;
+
+	priv = netdev_priv(net);
+	priv->can.bittiming_const = &mcp25xxfd_nominal_bittiming_const;
+	priv->can.do_set_bittiming = &mcp25xxfd_do_set_nominal_bittiming;
+	priv->can.data_bittiming_const = &mcp25xxfd_data_bittiming_const;
+	priv->can.do_set_data_bittiming = &mcp25xxfd_do_set_data_bittiming;
+	priv->can.do_set_mode = mcp25xxfd_do_set_mode;
+	priv->can.do_get_berr_counter = mcp25xxfd_get_berr_counter;
+
+	priv->can.ctrlmode_supported =
+		CAN_CTRLMODE_FD |
+		CAN_CTRLMODE_LOOPBACK |
+		CAN_CTRLMODE_LISTENONLY |
+		CAN_CTRLMODE_BERR_REPORTING |
+		CAN_CTRLMODE_FD_NON_ISO |
+		CAN_CTRLMODE_ONE_SHOT;
+
+	if (of_id)
+		priv->model = (enum mcp25xxfd_model)of_id->data;
+	else
+		priv->model = spi_get_device_id(spi)->driver_data;
+
+	spi_set_drvdata(spi, priv);
+	priv->spi = spi;
+	priv->net = net;
+	priv->clk = clk;
+
+	priv->clk_user_mask = MCP25XXFD_CLK_USER_CAN;
+
+	mutex_init(&priv->clk_user_lock);
+	mutex_init(&priv->spi_rxtx_lock);
+
+	/* enable the clock and mark as enabled */
+	priv->clk_user_mask = MCP25XXFD_CLK_USER_CAN;
+	ret = clk_prepare_enable(clk);
+	if (ret)
+		goto out_free;
+
+	/* all by default as push/pull */
+	priv->config.gpio_opendrain = false;
+
+	/* do not use the SCK clock divider of 2 */
+	priv->config.clock_div2 = false;
+
+	/* clock output is divided by 10 */
+	priv->config.clock_odiv = 10;
+
+	/* as a first guess we assume we are in CAN_CON_MODE_SLEEP
+	 * this is how we leave the controller when removing ourselves
+	 */
+	priv->active_can_mode = CAN_CON_MODE_SLEEP;
+
+	/* if we have a clock that is smaller then 4MHz, then enable the pll */
+	priv->config.clock_pll =
+		(freq <= MCP25XXFD_AUTO_PLL_MAX_CLOCK_FREQUENCY);
+
+	/* check in device tree for overrrides */
+	ret = mcp25xxfd_of_parse(priv);
+	if (ret)
+		return ret;
+
+	/* decide on real can clock rate */
+	priv->can.clock.freq = freq;
+	if (priv->config.clock_pll) {
+		priv->can.clock.freq *= MCP25XXFD_PLL_MULTIPLIER;
+		if (priv->can.clock.freq > MCP25XXFD_MAX_CLOCK_FREQUENCY) {
+			dev_err(&spi->dev,
+				"PLL clock frequency %i would exceed limit\n",
+				priv->can.clock.freq
+				);
+			return -EINVAL;
+		}
+	}
+	if (priv->config.clock_div2)
+		priv->can.clock.freq /= MCP25XXFD_SCLK_DIVIDER;
+
+	/* calclculate the clock frequencies to use */
+	priv->spi_setup_speed_hz = freq / 2;
+	priv->spi_speed_hz = priv->can.clock.freq / 2;
+	if (priv->config.clock_div2) {
+		priv->spi_setup_speed_hz /= MCP25XXFD_SCLK_DIVIDER;
+		priv->spi_speed_hz /= MCP25XXFD_SCLK_DIVIDER;
+	}
+
+	if (spi->max_speed_hz) {
+		priv->spi_setup_speed_hz = min_t(int,
+						 priv->spi_setup_speed_hz,
+						 spi->max_speed_hz);
+		priv->spi_speed_hz = min_t(int,
+					   priv->spi_speed_hz,
+					   spi->max_speed_hz);
+	}
+
+	/* Configure the SPI bus */
+	spi->bits_per_word = 8;
+	ret = spi_setup(spi);
+	if (ret)
+		goto out_clk;
+
+	priv->power = devm_regulator_get_optional(&spi->dev, "vdd");
+	priv->transceiver = devm_regulator_get_optional(&spi->dev, "xceiver");
+	if ((PTR_ERR(priv->power) == -EPROBE_DEFER) ||
+	    (PTR_ERR(priv->transceiver) == -EPROBE_DEFER)) {
+		ret = -EPROBE_DEFER;
+		goto out_clk;
+	}
+
+	ret = mcp25xxfd_power_enable(priv->power, 1);
+	if (ret)
+		goto out_clk;
+
+	SET_NETDEV_DEV(net, &spi->dev);
+
+	ret = mcp25xxfd_hw_probe(spi);
+	/* on error retry a second time */
+	if (ret == -ENODEV) {
+		ret = mcp25xxfd_hw_probe(spi);
+		if (!ret)
+			dev_info(&spi->dev,
+				 "found device only during retry\n");
+	}
+	if (ret) {
+		if (ret == -ENODEV)
+			dev_err(&spi->dev,
+				"Cannot initialize MCP%x. Wrong wiring?\n",
+				priv->model);
+		goto error_probe;
+	}
+
+	/* setting up GPIO+INT as PUSHPULL , TXCAN PUSH/PULL, no Standby */
+	priv->regs.iocon = 0;
+
+	/* SOF/CLOCKOUT pin 3 */
+	if (priv->config.clock_odiv < 1)
+		priv->regs.iocon |= MCP25XXFD_IOCON_SOF;
+
+	/* INT/GPIO (probably also clockout) as open drain */
+	if (priv->config.gpio_opendrain)
+		priv->regs.iocon |= MCP25XXFD_IOCON_INTOD;
+
+	ret = mcp25xxfd_cmd_write(spi, MCP25XXFD_IOCON, priv->regs.iocon,
+				  priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* and put controller to sleep */
+	mcp25xxfd_hw_sleep(spi);
+
+	ret = register_candev(net);
+	if (ret)
+		goto error_probe;
+
+	/* register debugfs */
+	mcp25xxfd_debugfs_add(priv);
+
+	devm_can_led_init(net);
+
+	netdev_info(net, "MCP%x successfully initialized.\n", priv->model);
+	return 0;
+
+error_probe:
+	mcp25xxfd_power_enable(priv->power, 0);
+
+out_clk:
+	mcp25xxfd_stop_clock(spi, MCP25XXFD_CLK_USER_CAN);
+
+out_free:
+	free_candev(net);
+	dev_err(&spi->dev, "Probe failed, err=%d\n", -ret);
+	return ret;
+}
+
+static int mcp25xxfd_can_remove(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct net_device *net = priv->net;
+
+	mcp25xxfd_debugfs_remove(priv);
+
+	unregister_candev(net);
+
+	mcp25xxfd_power_enable(priv->power, 0);
+
+	if (!IS_ERR(priv->clk))
+		clk_disable_unprepare(priv->clk);
+
+	free_candev(net);
+
+	return 0;
+}
+
+static int __maybe_unused mcp25xxfd_can_suspend(struct device *dev)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+	struct net_device *net = priv->net;
+
+	priv->force_quit = 1;
+	disable_irq(spi->irq);
+
+	if (netif_running(net)) {
+		netif_device_detach(net);
+
+		mcp25xxfd_hw_sleep(spi);
+		mcp25xxfd_power_enable(priv->transceiver, 0);
+		priv->after_suspend = AFTER_SUSPEND_UP;
+	} else {
+		priv->after_suspend = AFTER_SUSPEND_DOWN;
+	}
+
+	if (!IS_ERR_OR_NULL(priv->power)) {
+		regulator_disable(priv->power);
+		priv->after_suspend |= AFTER_SUSPEND_POWER;
+	}
+
+	return 0;
+}
+
+static int __maybe_unused mcp25xxfd_can_resume(struct device *dev)
+{
+	struct spi_device *spi = to_spi_device(dev);
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	if (priv->after_suspend & AFTER_SUSPEND_POWER)
+		mcp25xxfd_power_enable(priv->power, 1);
+
+	if (priv->after_suspend & AFTER_SUSPEND_UP)
+		mcp25xxfd_power_enable(priv->transceiver, 1);
+	else
+		priv->after_suspend = 0;
+
+	priv->force_quit = 0;
+
+	enable_irq(spi->irq);
+
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(mcp25xxfd_can_pm_ops, mcp25xxfd_can_suspend,
+	mcp25xxfd_can_resume);
+
+static struct spi_driver mcp25xxfd_can_driver = {
+	.driver = {
+		.name = DEVICE_NAME,
+		.of_match_table = mcp25xxfd_of_match,
+		.pm = &mcp25xxfd_can_pm_ops,
+	},
+	.id_table = mcp25xxfd_id_table,
+	.probe = mcp25xxfd_can_probe,
+	.remove = mcp25xxfd_can_remove,
+};
+module_spi_driver(mcp25xxfd_can_driver);
+
+MODULE_AUTHOR("Martin Sperl <kernel@martin.sperl.org>");
+MODULE_DESCRIPTION("Microchip 25XXFD CAN driver");
+MODULE_LICENSE("GPL v2");
--
2.11.0

^ permalink raw reply related

* [PATCH V4 3/3] can: mcp25xxfd: add gpiolib support for GPIO0/1 (aka. INT0/INT1)
From: kernel @ 2018-05-14 16:03 UTC (permalink / raw)
  To: linux-can, devicetree, Wolfgang Grandegger, Mark Kleine-Budde,
	Rob Herring, Mark Rutland
  Cc: Martin Sperl, Marcel Birthelmer, Sukkin Pang, Gerhard Bertelsmann
In-Reply-To: <20180514160313.29597-1-kernel@martin.sperl.org>

From: Martin Sperl <kernel@martin.sperl.org>

Add GPIOLIB support to mcp25xxfd.

Signed-off-by: Martin Sperl <kernel@martin.sperl.org>
Tested-by: Gerhard Bertelsmann <info@gerhard-bertelsmann.de>

---
Changelog:
  V1 -> V2: implemented support as per feedback
  V2 -> V3: moved dt-binding changes to patch 1
  V3 -> V4: resend

---
 drivers/net/can/spi/mcp25xxfd.c | 185 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 185 insertions(+)

diff --git a/drivers/net/can/spi/mcp25xxfd.c b/drivers/net/can/spi/mcp25xxfd.c
index 49a454da2196..10b60baddfb2 100644
--- a/drivers/net/can/spi/mcp25xxfd.c
+++ b/drivers/net/can/spi/mcp25xxfd.c
@@ -20,6 +20,7 @@
 #include <linux/device.h>
 #include <linux/dma-mapping.h>
 #include <linux/freezer.h>
+#include <linux/gpio/driver.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/jiffies.h>
@@ -806,9 +807,15 @@ struct mcp25xxfd_priv {
 	struct mutex clk_user_lock; /* lock for enabling/disabling the clock */
 	int clk_user_mask;
 #define MCP25XXFD_CLK_USER_CAN BIT(0)
+#define MCP25XXFD_CLK_USER_GPIO0 BIT(1)
+#define MCP25XXFD_CLK_USER_GPIO1 BIT(2)

 	struct dentry *debugfs_dir;

+#ifdef CONFIG_GPIOLIB
+	struct gpio_chip gpio;
+#endif
+
 	/* the actual model of the mcp25xxfd */
 	enum mcp25xxfd_model model;

@@ -1494,6 +1501,179 @@ static int mcp25xxfd_stop_clock(struct spi_device *spi, int requestor_mask)
 	return 0;
 }

+/* mcp25xxfd GPIO helper functions */
+#ifdef CONFIG_GPIOLIB
+
+enum mcp25xxfd_gpio_pins {
+	MCP25XXFD_GPIO_GPIO0 = 0,
+	MCP25XXFD_GPIO_GPIO1 = 1,
+};
+
+static int mcp25xxfd_gpio_request(struct gpio_chip *chip,
+				  unsigned int offset)
+{
+	struct mcp25xxfd_priv *priv = gpiochip_get_data(chip);
+	int clock_requestor = offset ?
+		MCP25XXFD_CLK_USER_GPIO1 : MCP25XXFD_CLK_USER_GPIO0;
+
+	/* only handle gpio 0/1 */
+	if (offset > 1)
+		return -EINVAL;
+
+	mcp25xxfd_start_clock(priv->spi, clock_requestor);
+
+	return 0;
+}
+
+static void mcp25xxfd_gpio_free(struct gpio_chip *chip,
+				unsigned int offset)
+{
+	struct mcp25xxfd_priv *priv = gpiochip_get_data(chip);
+	int clock_requestor = offset ?
+		MCP25XXFD_CLK_USER_GPIO1 : MCP25XXFD_CLK_USER_GPIO0;
+
+	/* only handle gpio 0/1 */
+	if (offset > 1)
+		return;
+
+	mcp25xxfd_stop_clock(priv->spi, clock_requestor);
+}
+
+static int mcp25xxfd_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+	struct mcp25xxfd_priv *priv = gpiochip_get_data(chip);
+	u32 mask = (offset) ? MCP25XXFD_IOCON_GPIO1 : MCP25XXFD_IOCON_GPIO0;
+	int ret;
+
+	/* only handle gpio 0/1 */
+	if (offset > 1)
+		return -EINVAL;
+
+	/* read the relevant gpio Latch */
+	ret = mcp25xxfd_cmd_read_mask(priv->spi, MCP25XXFD_IOCON,
+				      &priv->regs.iocon, mask,
+				      priv->spi_setup_speed_hz);
+	if (ret)
+		return ret;
+
+	/* return the match */
+	return priv->regs.iocon & mask;
+}
+
+static void mcp25xxfd_gpio_set(struct gpio_chip *chip, unsigned int offset,
+			       int value)
+{
+	struct mcp25xxfd_priv *priv = gpiochip_get_data(chip);
+	u32 mask = (offset) ? MCP25XXFD_IOCON_LAT1 : MCP25XXFD_IOCON_LAT0;
+
+	/* only handle gpio 0/1 */
+	if (offset > 1)
+		return;
+
+	/* update in memory representation with the corresponding value */
+	if (value)
+		priv->regs.iocon |= mask;
+	else
+		priv->regs.iocon &= ~mask;
+
+	mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_IOCON,
+				 priv->regs.iocon, mask,
+				 priv->spi_setup_speed_hz);
+}
+
+static int mcp25xxfd_gpio_direction_input(struct gpio_chip *chip,
+					  unsigned int offset)
+{
+	struct mcp25xxfd_priv *priv = gpiochip_get_data(chip);
+	u32 mask_tri = (offset) ?
+		MCP25XXFD_IOCON_TRIS1 : MCP25XXFD_IOCON_TRIS0;
+	u32 mask_stby = (offset) ?
+		0 : MCP25XXFD_IOCON_XSTBYEN;
+	u32 mask_pm = (offset) ?
+		MCP25XXFD_IOCON_PM1 : MCP25XXFD_IOCON_PM0;
+
+	/* only handle gpio 0/1 */
+	if (offset > 1)
+		return -EINVAL;
+
+	/* set the mask */
+	priv->regs.iocon |= mask_tri | mask_pm;
+
+	/* clear stby */
+	priv->regs.iocon &= ~mask_stby;
+
+	return mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_IOCON,
+					priv->regs.iocon,
+					mask_tri | mask_stby | mask_pm,
+					priv->spi_setup_speed_hz);
+}
+
+static int mcp25xxfd_gpio_direction_output(struct gpio_chip *chip,
+					   unsigned int offset, int value)
+{
+	struct mcp25xxfd_priv *priv = gpiochip_get_data(chip);
+	u32 mask_tri = (offset) ?
+		MCP25XXFD_IOCON_TRIS1 : MCP25XXFD_IOCON_TRIS0;
+	u32 mask_lat = (offset) ?
+		MCP25XXFD_IOCON_LAT1 : MCP25XXFD_IOCON_LAT0;
+	u32 mask_pm = (offset) ?
+		MCP25XXFD_IOCON_PM1 : MCP25XXFD_IOCON_PM0;
+	u32 mask_stby = (offset) ?
+		0 : MCP25XXFD_IOCON_XSTBYEN;
+
+	/* only handle gpio 0/1 */
+	if (offset > 1)
+		return -EINVAL;
+
+	/* clear the tristate bit and also clear stby */
+	priv->regs.iocon &= ~(mask_tri | mask_stby);
+
+	/* set GPIO mode */
+	priv->regs.iocon |= mask_pm;
+
+	/* set the value */
+	if (value)
+		priv->regs.iocon |= mask_lat;
+	else
+		priv->regs.iocon &= ~mask_lat;
+
+	return mcp25xxfd_cmd_write_mask(priv->spi, MCP25XXFD_IOCON,
+					priv->regs.iocon,
+					mask_tri | mask_lat |
+					mask_pm | mask_stby,
+					priv->spi_setup_speed_hz);
+}
+
+static int mcp25xxfd_gpio_setup(struct spi_device *spi)
+{
+	struct mcp25xxfd_priv *priv = spi_get_drvdata(spi);
+
+	/* gpiochip only handles GPIO0 and GPIO1 */
+	priv->gpio.owner		= THIS_MODULE;
+	priv->gpio.parent		= &spi->dev;
+	priv->gpio.label		= dev_name(&spi->dev);
+	priv->gpio.direction_input	= mcp25xxfd_gpio_direction_input;
+	priv->gpio.get			= mcp25xxfd_gpio_get;
+	priv->gpio.direction_output	= mcp25xxfd_gpio_direction_output;
+	priv->gpio.set			= mcp25xxfd_gpio_set;
+	priv->gpio.request		= mcp25xxfd_gpio_request;
+	priv->gpio.free			= mcp25xxfd_gpio_free;
+	priv->gpio.base			= -1;
+	priv->gpio.ngpio		= 2;
+	priv->gpio.can_sleep		= 1;
+
+	return devm_gpiochip_add_data(&spi->dev, &priv->gpio, priv);
+}
+
+#else
+
+static int mcp25xxfd_gpio_setup(struct spi_device *spi)
+{
+	return 0;
+}
+
+#endif
+
 /* ideally these would be defined in uapi/linux/can.h */
 #define CAN_EFF_SID_SHIFT		(CAN_EFF_ID_BITS - CAN_SFF_ID_BITS)
 #define CAN_EFF_SID_BITS		CAN_SFF_ID_BITS
@@ -4104,6 +4284,11 @@ static int mcp25xxfd_can_probe(struct spi_device *spi)
 	if (ret)
 		goto out_free;

+	/* Setup GPIO controller */
+	ret = mcp25xxfd_gpio_setup(spi);
+	if (ret)
+		goto out_clk;
+
 	/* all by default as push/pull */
 	priv->config.gpio_opendrain = false;

--
2.11.0

^ permalink raw reply related

* Re: [PATCH v2 2/4] ARM: dtsi: add pwm node for sun8i.
From: Hao Zhang @ 2018-05-14 16:08 UTC (permalink / raw)
  To: André Przywara
  Cc: Thierry Reding, robh+dt-DgEjT+Ai2ygdnm+yROfE0A, Mark Rutland,
	Chen-Yu Tsai, Maxime Ripard, linux-I+IVW8TIWO2tmTQ+vhA3Yw,
	linux-gpio-u79uwXL29TY76Z2rM5mHXA, open list,
	open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
	moderated list:ARM/Allwinner sunXi SoC support,
	linux-pwm-u79uwXL29TY76Z2rM5mHXA,
	linux-sunxi-/JYPxA39Uh5TLH3MbocFFw
In-Reply-To: <b8af4f6a-c2df-7382-9a1c-9c1a9ea5f8c4-5wv7dgnIgG8@public.gmane.org>

2018-02-28 9:53 GMT+08:00 André Przywara <andre.przywara-5wv7dgnIgG8@public.gmane.org>:
> Hi,
>
> The subject line should mention the R40, there are far too many sun8i SoCs.

Okey.

>
> On 25/02/18 13:51, hao_zhang wrote:
>> This patch adds pwm node for sun8i.
>>
>> Signed-off-by: hao_zhang <hao5781286-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org>
>> ---
>>  arch/arm/boot/dts/sun8i-r40.dtsi | 13 +++++++++++++
>>  1 file changed, 13 insertions(+)
>>
>> diff --git a/arch/arm/boot/dts/sun8i-r40.dtsi b/arch/arm/boot/dts/sun8i-r40.dtsi
>> index 173dcc1..99a0261 100644
>> --- a/arch/arm/boot/dts/sun8i-r40.dtsi
>> +++ b/arch/arm/boot/dts/sun8i-r40.dtsi
>> @@ -295,6 +295,11 @@
>>                               bias-pull-up;
>>                       };
>>
>> +                     pwm_ch0_pin: pwm-ch0-pin {
>> +                             pins = "PB2";
>> +                             function = "pwm";
>> +                     };
>> +
>>                       uart0_pb_pins: uart0-pb-pins {
>>                               pins = "PB22", "PB23";
>>                               function = "uart0";
>> @@ -306,6 +311,14 @@
>>                       reg = <0x01c20c90 0x10>;
>>               };
>>
>> +             pwm: pwm@1c23400 {
>> +                          compatible = "allwinner,sun8i-r40-pwm";
>> +                          reg = <0x01c23400 0x154>;
>
> Following my comments on the binding document:
>                         interrupts = <GIC_SPI 47 IRQ_TYPE_LEVEL_HIGH>;
>
>> +                          clocks = <&osc24M>;
>
> And possibly multiple clocks here (though I fail to find the APB1 clock
> being exposed by our CCU).

It seem CCU dosen't support APB1 clock for R40 PWM...

>
> Cheers,
> Andre.
>
>> +                          #pwm-cells = <3>;
>> +                          status = "disabled";
>> +             };
>> +
>>               uart0: serial@1c28000 {
>>                       compatible = "snps,dw-apb-uart";
>>                       reg = <0x01c28000 0x400>;
>>
>

-- 
You received this message because you are subscribed to the Google Groups "linux-sunxi" group.
To unsubscribe from this group and stop receiving emails from it, send an email to linux-sunxi+unsubscribe-/JYPxA39Uh5TLH3MbocFF+G/Ez6ZCGd0@public.gmane.org
For more options, visit https://groups.google.com/d/optout.

^ permalink raw reply

* [PATCH 0/4] Add DesignWare EP support
From: Gustavo Pimentel @ 2018-05-14 16:17 UTC (permalink / raw)
  To: bhelgaas, lorenzo.pieralisi, Joao.Pinto, jingoohan1, kishon,
	robh+dt, mark.rutland
  Cc: linux-pci, linux-kernel, devicetree, Gustavo Pimentel

The PCIe controller dual mode is capable of operating in RC mode as well
as EP mode by configuration option. Till now only RC mode was supported,
with this patch is add EP support to the DesignWare driver.

Gustavo Pimentel (4):
  bindings: PCI: designware: Example update
  PCI: dwc: Add support for EP mode
  bindings: PCI: designware: Add support for EP in DesignWare driver
  misc: pci_endpoint_test: Add DesignWare EP entry

 .../devicetree/bindings/pci/designware-pcie.txt    |  24 +++-
 drivers/misc/pci_endpoint_test.c                   |   1 +
 drivers/pci/dwc/Kconfig                            |  41 ++++--
 drivers/pci/dwc/pcie-designware-ep.c               |   3 +
 drivers/pci/dwc/pcie-designware-plat.c             | 149 +++++++++++++++++++--
 drivers/pci/endpoint/functions/pci-epf-test.c      |   7 +
 include/linux/pci-epc.h                            |   8 ++
 7 files changed, 206 insertions(+), 27 deletions(-)

-- 
2.7.4

^ permalink raw reply

* [PATCH 1/4] bindings: PCI: designware: Example update
From: Gustavo Pimentel @ 2018-05-14 16:17 UTC (permalink / raw)
  To: bhelgaas, lorenzo.pieralisi, Joao.Pinto, jingoohan1, kishon,
	robh+dt, mark.rutland
  Cc: linux-pci, linux-kernel, devicetree, Gustavo Pimentel
In-Reply-To: <cover.1526314398.git.gustavo.pimentel@synopsys.com>

Replace "ctrlreg" reg-name by "dbi" to be coherent with similar drivers,
however it still be compatible with any previous DT that uses the old
reg-name.

Replace the PCIe base address example by a real PCIe base address in use.

Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/pci/designware-pcie.txt | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/Documentation/devicetree/bindings/pci/designware-pcie.txt b/Documentation/devicetree/bindings/pci/designware-pcie.txt
index 1da7ade..7f9804d 100644
--- a/Documentation/devicetree/bindings/pci/designware-pcie.txt
+++ b/Documentation/devicetree/bindings/pci/designware-pcie.txt
@@ -1,7 +1,8 @@
 * Synopsys DesignWare PCIe interface
 
 Required properties:
-- compatible: should contain "snps,dw-pcie" to identify the core.
+- compatible:
+	"snps,dw-pcie" for RC mode;
 - reg: Should contain the configuration address space.
 - reg-names: Must be "config" for the PCIe configuration space.
     (The old way of getting the configuration address space from "ranges"
@@ -41,11 +42,11 @@ EP mode:
 
 Example configuration:
 
-	pcie: pcie@dffff000 {
+	pcie: pcie@dfc00000 {
 		compatible = "snps,dw-pcie";
-		reg = <0xdffff000 0x1000>, /* Controller registers */
-		      <0xd0000000 0x2000>; /* PCI config space */
-		reg-names = "ctrlreg", "config";
+		reg = <0xdfc00000 0x0001000>, /* IP registers */
+		      <0xd0000000 0x0002000>; /* Configuration space */
+		reg-names = "dbi", "config";
 		#address-cells = <3>;
 		#size-cells = <2>;
 		device_type = "pci";
@@ -54,5 +55,4 @@ Example configuration:
 		interrupts = <25>, <24>;
 		#interrupt-cells = <1>;
 		num-lanes = <1>;
-		num-viewport = <3>;
 	};
-- 
2.7.4

^ permalink raw reply related

* [PATCH 2/4] PCI: dwc: Add support for EP mode
From: Gustavo Pimentel @ 2018-05-14 16:17 UTC (permalink / raw)
  To: bhelgaas, lorenzo.pieralisi, Joao.Pinto, jingoohan1, kishon,
	robh+dt, mark.rutland
  Cc: linux-pci, linux-kernel, devicetree, Gustavo Pimentel
In-Reply-To: <cover.1526314398.git.gustavo.pimentel@synopsys.com>

The PCIe controller dual mode is capable of operating in RC mode as well
as EP mode by configuration option. Till now only RC mode was supported,
with this patch is add EP support to the DesignWare driver.

Add new property on pci_epc structure which allow to configure
pci_epf_test driver accordingly to the controller specific requirements.

Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Acked-by: Kishon Vijay Abraham I <kishon@ti.com>
---
 drivers/pci/dwc/Kconfig                       |  41 +++++--
 drivers/pci/dwc/pcie-designware-ep.c          |   3 +
 drivers/pci/dwc/pcie-designware-plat.c        | 149 ++++++++++++++++++++++++--
 drivers/pci/endpoint/functions/pci-epf-test.c |   7 ++
 include/linux/pci-epc.h                       |   8 ++
 5 files changed, 187 insertions(+), 21 deletions(-)

diff --git a/drivers/pci/dwc/Kconfig b/drivers/pci/dwc/Kconfig
index 2f3f5c5..62f7cdf 100644
--- a/drivers/pci/dwc/Kconfig
+++ b/drivers/pci/dwc/Kconfig
@@ -7,8 +7,7 @@ config PCIE_DW
 
 config PCIE_DW_HOST
         bool
-	depends on PCI
-	depends on PCI_MSI_IRQ_DOMAIN
+	depends on PCI && PCI_MSI_IRQ_DOMAIN
         select PCIE_DW
 
 config PCIE_DW_EP
@@ -51,17 +50,37 @@ config PCI_DRA7XX_EP
 	  This uses the DesignWare core.
 
 config PCIE_DW_PLAT
-	bool "Platform bus based DesignWare PCIe Controller"
-	depends on PCI
-	depends on PCI_MSI_IRQ_DOMAIN
-	select PCIE_DW_HOST
-	---help---
-	 This selects the DesignWare PCIe controller support. Select this if
-	 you have a PCIe controller on Platform bus.
+	bool
 
-	 If you have a controller with this interface, say Y or M here.
+config PCIE_DW_PLAT_HOST
+	bool "Platform bus based DesignWare PCIe Controller - Host mode"
+	depends on PCI && PCI_MSI_IRQ_DOMAIN
+	select PCIE_DW_HOST
+	select PCIE_DW_PLAT
+	default y
+	help
+	  Enables support for the PCIe controller in the Designware IP to
+	  work in host mode. There are two instances of PCIe controller in
+	  Designware IP.
+	  This controller can work either as EP or RC. In order to enable
+	  host-specific features PCIE_DW_PLAT_HOST must be selected and in
+	  order to enable device-specific features PCI_DW_PLAT_EP must be
+	  selected.
 
-	 If unsure, say N.
+config PCIE_DW_PLAT_EP
+	bool "Platform bus based DesignWare PCIe Controller - Endpoint mode"
+	depends on PCI && PCI_MSI_IRQ_DOMAIN
+	depends on PCI_ENDPOINT
+	select PCIE_DW_EP
+	select PCIE_DW_PLAT
+	help
+	  Enables support for the PCIe controller in the Designware IP to
+	  work in endpoint mode. There are two instances of PCIe controller
+	  in Designware IP.
+	  This controller can work either as EP or RC. In order to enable
+	  host-specific features PCIE_DW_PLAT_HOST must be selected and in
+	  order to enable device-specific features PCI_DW_PLAT_EP must be
+	  selected.
 
 config PCI_EXYNOS
 	bool "Samsung Exynos PCIe controller"
diff --git a/drivers/pci/dwc/pcie-designware-ep.c b/drivers/pci/dwc/pcie-designware-ep.c
index f07678b..2c05a08 100644
--- a/drivers/pci/dwc/pcie-designware-ep.c
+++ b/drivers/pci/dwc/pcie-designware-ep.c
@@ -411,6 +411,9 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep)
 		return -ENOMEM;
 	}
 
+	epc->features = EPC_FEATURE_NO_LINKUP_NOTIFIER;
+	EPC_FEATURE_SET_BAR(epc->features, BAR0);
+
 	ep->epc = epc;
 	epc_set_drvdata(epc, ep);
 	dw_pcie_setup(pci);
diff --git a/drivers/pci/dwc/pcie-designware-plat.c b/drivers/pci/dwc/pcie-designware-plat.c
index 5416aa8..efc315c 100644
--- a/drivers/pci/dwc/pcie-designware-plat.c
+++ b/drivers/pci/dwc/pcie-designware-plat.c
@@ -12,19 +12,29 @@
 #include <linux/interrupt.h>
 #include <linux/kernel.h>
 #include <linux/init.h>
+#include <linux/of_device.h>
 #include <linux/of_gpio.h>
 #include <linux/pci.h>
 #include <linux/platform_device.h>
 #include <linux/resource.h>
 #include <linux/signal.h>
 #include <linux/types.h>
+#include <linux/regmap.h>
 
 #include "pcie-designware.h"
 
 struct dw_plat_pcie {
-	struct dw_pcie		*pci;
+	struct dw_pcie			*pci;
+	struct regmap			*regmap;
+	enum dw_pcie_device_mode	mode;
 };
 
+struct dw_plat_pcie_of_data {
+	enum dw_pcie_device_mode	mode;
+};
+
+static const struct of_device_id dw_plat_pcie_of_match[];
+
 static int dw_plat_pcie_host_init(struct pcie_port *pp)
 {
 	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
@@ -42,9 +52,53 @@ static const struct dw_pcie_host_ops dw_plat_pcie_host_ops = {
 	.host_init = dw_plat_pcie_host_init,
 };
 
-static int dw_plat_add_pcie_port(struct pcie_port *pp,
+static int dw_plat_pcie_establish_link(struct dw_pcie *pci)
+{
+	return 0;
+}
+
+static const struct dw_pcie_ops dw_pcie_ops = {
+	.start_link = dw_plat_pcie_establish_link,
+};
+
+static void dw_plat_pcie_ep_init(struct dw_pcie_ep *ep)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+	enum pci_barno bar;
+
+	for (bar = BAR_0; bar <= BAR_5; bar++)
+		dw_pcie_ep_reset_bar(pci, bar);
+}
+
+static int dw_plat_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
+				     enum pci_epc_irq_type type,
+				     u8 interrupt_num)
+{
+	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
+
+	switch (type) {
+	case PCI_EPC_IRQ_LEGACY:
+		dev_err(pci->dev, "EP cannot trigger legacy IRQs\n");
+		return -EINVAL;
+	case PCI_EPC_IRQ_MSI:
+		return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
+	default:
+		dev_err(pci->dev, "UNKNOWN IRQ type\n");
+	}
+
+	return 0;
+}
+
+static struct dw_pcie_ep_ops pcie_ep_ops = {
+	.ep_init = dw_plat_pcie_ep_init,
+	.raise_irq = dw_plat_pcie_ep_raise_irq,
+};
+
+static int dw_plat_add_pcie_port(struct dw_plat_pcie *dw_plat_pcie,
 				 struct platform_device *pdev)
 {
+	struct dw_pcie *pci = dw_plat_pcie->pci;
+	struct pcie_port *pp = &pci->pp;
 	struct device *dev = &pdev->dev;
 	int ret;
 
@@ -63,15 +117,44 @@ static int dw_plat_add_pcie_port(struct pcie_port *pp,
 
 	ret = dw_pcie_host_init(pp);
 	if (ret) {
-		dev_err(dev, "failed to initialize host\n");
+		dev_err(dev, "Failed to initialize host\n");
 		return ret;
 	}
 
 	return 0;
 }
 
-static const struct dw_pcie_ops dw_pcie_ops = {
-};
+static int dw_plat_add_pcie_ep(struct dw_plat_pcie *dw_plat_pcie,
+			       struct platform_device *pdev)
+{
+	int ret;
+	struct dw_pcie_ep *ep;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+	struct dw_pcie *pci = dw_plat_pcie->pci;
+
+	ep = &pci->ep;
+	ep->ops = &pcie_ep_ops;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi2");
+	pci->dbi_base2 = devm_ioremap_resource(dev, res);
+	if (IS_ERR(pci->dbi_base2))
+		return PTR_ERR(pci->dbi_base2);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
+	if (!res)
+		return -EINVAL;
+
+	ep->phys_base = res->start;
+	ep->addr_size = resource_size(res);
+
+	ret = dw_pcie_ep_init(ep);
+	if (ret) {
+		dev_err(dev, "Failed to initialize endpoint\n");
+		return ret;
+	}
+	return 0;
+}
 
 static int dw_plat_pcie_probe(struct platform_device *pdev)
 {
@@ -80,6 +163,16 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
 	struct dw_pcie *pci;
 	struct resource *res;  /* Resource from DT */
 	int ret;
+	const struct of_device_id *match;
+	const struct dw_plat_pcie_of_data *data;
+	enum dw_pcie_device_mode mode;
+
+	match = of_match_device(dw_plat_pcie_of_match, dev);
+	if (!match)
+		return -EINVAL;
+
+	data = (struct dw_plat_pcie_of_data *)match->data;
+	mode = (enum dw_pcie_device_mode)data->mode;
 
 	dw_plat_pcie = devm_kzalloc(dev, sizeof(*dw_plat_pcie), GFP_KERNEL);
 	if (!dw_plat_pcie)
@@ -93,23 +186,59 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
 	pci->ops = &dw_pcie_ops;
 
 	dw_plat_pcie->pci = pci;
+	dw_plat_pcie->mode = mode;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
+	if (!res)
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 
-	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	pci->dbi_base = devm_ioremap_resource(dev, res);
 	if (IS_ERR(pci->dbi_base))
 		return PTR_ERR(pci->dbi_base);
 
 	platform_set_drvdata(pdev, dw_plat_pcie);
 
-	ret = dw_plat_add_pcie_port(&pci->pp, pdev);
-	if (ret < 0)
-		return ret;
+	switch (dw_plat_pcie->mode) {
+	case DW_PCIE_RC_TYPE:
+		if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_HOST))
+			return -ENODEV;
+
+		ret = dw_plat_add_pcie_port(dw_plat_pcie, pdev);
+		if (ret < 0)
+			return ret;
+		break;
+	case DW_PCIE_EP_TYPE:
+		if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_EP))
+			return -ENODEV;
+
+		ret = dw_plat_add_pcie_ep(dw_plat_pcie, pdev);
+		if (ret < 0)
+			return ret;
+		break;
+	default:
+		dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->mode);
+	}
 
 	return 0;
 }
 
+static const struct dw_plat_pcie_of_data dw_plat_pcie_rc_of_data = {
+	.mode = DW_PCIE_RC_TYPE,
+};
+
+static const struct dw_plat_pcie_of_data dw_plat_pcie_ep_of_data = {
+	.mode = DW_PCIE_EP_TYPE,
+};
+
 static const struct of_device_id dw_plat_pcie_of_match[] = {
-	{ .compatible = "snps,dw-pcie", },
+	{
+		.compatible = "snps,dw-pcie",
+		.data = &dw_plat_pcie_rc_of_data,
+	},
+	{
+		.compatible = "snps,dw-pcie-ep",
+		.data = &dw_plat_pcie_ep_of_data,
+	},
 	{},
 };
 
diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
index 7cef851..bee401d 100644
--- a/drivers/pci/endpoint/functions/pci-epf-test.c
+++ b/drivers/pci/endpoint/functions/pci-epf-test.c
@@ -435,6 +435,13 @@ static int pci_epf_test_bind(struct pci_epf *epf)
 	if (WARN_ON_ONCE(!epc))
 		return -EINVAL;
 
+	if (epc->features & EPC_FEATURE_NO_LINKUP_NOTIFIER)
+		epf_test->linkup_notifier = false;
+	else
+		epf_test->linkup_notifier = true;
+
+	epf_test->test_reg_bar = EPC_FEATURE_GET_BAR(epc->features);
+
 	ret = pci_epc_write_header(epc, epf->func_no, header);
 	if (ret) {
 		dev_err(dev, "configuration header write failed\n");
diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
index af657ca..243eaa5 100644
--- a/include/linux/pci-epc.h
+++ b/include/linux/pci-epc.h
@@ -90,8 +90,16 @@ struct pci_epc {
 	struct config_group		*group;
 	/* spinlock to protect against concurrent access of EP controller */
 	spinlock_t			lock;
+	unsigned int			features;
 };
 
+#define EPC_FEATURE_NO_LINKUP_NOTIFIER		BIT(0)
+#define EPC_FEATURE_BAR_MASK			(BIT(1) | BIT(2) | BIT(3))
+#define EPC_FEATURE_SET_BAR(features, bar)	\
+		(features |= (EPC_FEATURE_BAR_MASK & (bar << 1)))
+#define EPC_FEATURE_GET_BAR(features)		\
+		((features & EPC_FEATURE_BAR_MASK) >> 1)
+
 #define to_pci_epc(device) container_of((device), struct pci_epc, dev)
 
 #define pci_epc_create(dev, ops)    \
-- 
2.7.4

^ permalink raw reply related

* [PATCH 3/4] bindings: PCI: designware: Add support for EP in DesignWare driver
From: Gustavo Pimentel @ 2018-05-14 16:17 UTC (permalink / raw)
  To: bhelgaas, lorenzo.pieralisi, Joao.Pinto, jingoohan1, kishon,
	robh+dt, mark.rutland
  Cc: linux-pci, linux-kernel, devicetree, Gustavo Pimentel
In-Reply-To: <cover.1526314398.git.gustavo.pimentel@synopsys.com>

Add device tree binding documentation for the EP in PCIe DesignWare driver.

Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Reviewed-by: Rob Herring <robh@kernel.org>
---
 Documentation/devicetree/bindings/pci/designware-pcie.txt | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/Documentation/devicetree/bindings/pci/designware-pcie.txt b/Documentation/devicetree/bindings/pci/designware-pcie.txt
index 7f9804d..c124f9b 100644
--- a/Documentation/devicetree/bindings/pci/designware-pcie.txt
+++ b/Documentation/devicetree/bindings/pci/designware-pcie.txt
@@ -3,6 +3,7 @@
 Required properties:
 - compatible:
 	"snps,dw-pcie" for RC mode;
+	"snps,dw-pcie-ep" for EP mode;
 - reg: Should contain the configuration address space.
 - reg-names: Must be "config" for the PCIe configuration space.
     (The old way of getting the configuration address space from "ranges"
@@ -56,3 +57,14 @@ Example configuration:
 		#interrupt-cells = <1>;
 		num-lanes = <1>;
 	};
+or
+	pcie: pcie@dfc00000 {
+		compatible = "snps,dw-pcie-ep";
+		reg = <0xdfc00000 0x0001000>, /* IP registers 1 */
+		      <0xdfc01000 0x0001000>, /* IP registers 2 */
+		      <0xd0000000 0x2000000>; /* Configuration space */
+		reg-names = "dbi", "dbi2", "addr_space";
+		num-ib-windows = <6>;
+		num-ob-windows = <2>;
+		num-lanes = <1>;
+	};
-- 
2.7.4

^ permalink raw reply related

* [PATCH 4/4] misc: pci_endpoint_test: Add DesignWare EP entry
From: Gustavo Pimentel @ 2018-05-14 16:17 UTC (permalink / raw)
  To: bhelgaas, lorenzo.pieralisi, Joao.Pinto, jingoohan1, kishon,
	robh+dt, mark.rutland
  Cc: linux-pci, linux-kernel, devicetree, Gustavo Pimentel
In-Reply-To: <cover.1526314398.git.gustavo.pimentel@synopsys.com>

Add the DesignWare EP device ID entry to pci_endpoint_test driver table.

Allow the device to be recognize and handle by the pci_endpoint_test
driver.

Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Acked-by: Kishon Vijay Abraham I <kishon@ti.com>
---
 drivers/misc/pci_endpoint_test.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/misc/pci_endpoint_test.c b/drivers/misc/pci_endpoint_test.c
index fe8897e..58a88ba 100644
--- a/drivers/misc/pci_endpoint_test.c
+++ b/drivers/misc/pci_endpoint_test.c
@@ -634,6 +634,7 @@ static void pci_endpoint_test_remove(struct pci_dev *pdev)
 static const struct pci_device_id pci_endpoint_test_tbl[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA74x) },
 	{ PCI_DEVICE(PCI_VENDOR_ID_TI, PCI_DEVICE_ID_TI_DRA72x) },
+	{ PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, 0xedda) },
 	{ }
 };
 MODULE_DEVICE_TABLE(pci, pci_endpoint_test_tbl);
-- 
2.7.4

^ permalink raw reply related

* [patch v20 0/4] JTAG driver introduction
From: Oleksandr Shamray @ 2018-05-14 16:19 UTC (permalink / raw)
  To: gregkh, arnd
  Cc: linux-kernel, linux-arm-kernel, devicetree, openbmc, joel, jiri,
	tklauser, linux-serial, vadimp, system-sw-low-level, robh+dt,
	openocd-devel-owner, linux-api, davem, mchehab, Oleksandr Shamray

When a need raise up to use JTAG interface for system's devices
programming or CPU debugging, usually the user layer
application implements jtag protocol by bit-bang or using a 
proprietary connection to vendor hardware.
This method can be slow and not generic.
 
We propose to implement general JTAG interface and infrastructure
to communicate with user layer application. In such way, we can
have the standard JTAG interface core part and separation from
specific HW implementation.
This allow new capability to debug the CPU or program system's 
device via BMC without additional devices nor cost. 

This patch purpose is to add JTAG master core infrastructure by 
defining new JTAG class and provide generic JTAG interface
to allow hardware specific drivers to connect this interface.
This will enable all JTAG drivers to use the common interface
part and will have separate for hardware implementation.

The JTAG (Joint Test Action Group) core driver provides minimal generic
JTAG interface, which can be used by hardware specific JTAG master
controllers. By providing common interface for the JTAG controllers,
user space device programing is hardware independent.
 
Modern SoC which in use for embedded system' equipped with
internal JTAG master interface.
This interface is used for programming and debugging system's
hardware components, like CPLD, FPGA, CPU, voltage and
industrial controllers.
Firmware for such devices can be upgraded through JTAG interface during
Runtime. The JTAG standard support for multiple devices programming,
is in case their lines are daisy-chained together.

For example, systems which equipped with host CPU, BMC SoC or/and 
number of programmable devices are capable to connect a pin and
select system components dynamically for programming and debugging,
This is using by the BMC which is equipped with internal SoC master
controller.
For example:

BMC JTAG master --> pin selected to CPLDs chain for programming (filed
upgrade, production) 
BMC JTAG master --> pin selected to voltage monitors for programming 
(field upgrade, production) 
BMC JTAG master --> pin selected to host CPU (on-site debugging 
and developers debugging)

For example, we can have application in user space which using calls
to JTAG driver executes CPLD programming directly from SVF file
 
The JTAG standard (IEEE 1149.1) defines the next connector pins:
- TDI (Test Data In);
- TDO (Test Data Out);
- TCK (Test Clock);
- TMS (Test Mode Select);
- TRST (Test Reset) (Optional);

The SoC equipped with JTAG master controller, performs
device programming on command or vector level. For example
a file in a standard SVF (Serial Vector Format) that contains
boundary scan vectors, can be used by sending each vector
to the JTAG interface and the JTAG controller will execute
the programming.

Initial version provides the system calls set for:
- SIR (Scan Instruction Register, IEEE 1149.1 Instruction Register scan);
- SDR (Scan Data Register, IEEE 1149.1 Data Register scan);
- RUNTEST (Forces the IEEE 1149.1 bus to a run state for a specified
  number of clocks.

SoC which are not equipped with JTAG master interface, can be built
on top of JTAG core driver infrastructure, by applying bit-banging of
TDI, TDO, TCK and TMS pins within the hardware specific driver.

Oleksandr Shamray (4):
  drivers: jtag: Add JTAG core driver
  drivers: jtag: Add Aspeed SoC 24xx and 25xx families JTAG master
    driver
  Documentation: jtag: Add bindings for Aspeed SoC 24xx and 25xx
    families     JTAG master driver
  Documentation: jtag: Add ABI documentation

 Documentation/ABI/testing/jtag-dev                 |   27 +
 .../devicetree/bindings/jtag/aspeed-jtag.txt       |   22 +
 Documentation/ioctl/ioctl-number.txt               |    2 +
 Documentation/jtag/overview                        |   28 +
 Documentation/jtag/transactions                    |  109 +++
 MAINTAINERS                                        |   10 +
 drivers/Kconfig                                    |    2 +
 drivers/Makefile                                   |    1 +
 drivers/jtag/Kconfig                               |   32 +
 drivers/jtag/Makefile                              |    2 +
 drivers/jtag/jtag-aspeed.c                         |  786 ++++++++++++++++++++
 drivers/jtag/jtag.c                                |  274 +++++++
 include/linux/jtag.h                               |   41 +
 include/uapi/linux/jtag.h                          |  105 +++
 14 files changed, 1441 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/jtag-dev
 create mode 100644 Documentation/devicetree/bindings/jtag/aspeed-jtag.txt
 create mode 100644 Documentation/jtag/overview
 create mode 100644 Documentation/jtag/transactions
 create mode 100644 drivers/jtag/Kconfig
 create mode 100644 drivers/jtag/Makefile
 create mode 100644 drivers/jtag/jtag-aspeed.c
 create mode 100644 drivers/jtag/jtag.c
 create mode 100644 include/linux/jtag.h
 create mode 100644 include/uapi/linux/jtag.h

^ permalink raw reply

* [patch v20 1/4] drivers: jtag: Add JTAG core driver
From: Oleksandr Shamray @ 2018-05-14 16:20 UTC (permalink / raw)
  To: gregkh, arnd
  Cc: linux-kernel, linux-arm-kernel, devicetree, openbmc, joel, jiri,
	tklauser, linux-serial, vadimp, system-sw-low-level, robh+dt,
	openocd-devel-owner, linux-api, davem, mchehab, Oleksandr Shamray,
	Jiri Pirko
In-Reply-To: <1526314803-3532-1-git-send-email-oleksandrs@mellanox.com>

Initial patch for JTAG driver
JTAG class driver provide infrastructure to support hardware/software
JTAG platform drivers. It provide user layer API interface for flashing
and debugging external devices which equipped with JTAG interface
using standard transactions.

Driver exposes set of IOCTL to user space for:
- XFER:
- SIR (Scan Instruction Register, IEEE 1149.1 Data Register scan);
- SDR (Scan Data Register, IEEE 1149.1 Instruction Register scan);
- RUNTEST (Forces the IEEE 1149.1 bus to a run state for a specified
  number of clocks).
- SIOCFREQ/GIOCFREQ for setting and reading JTAG frequency.

Driver core provides set of internal APIs for allocation and
registration:
- jtag_register;
- jtag_unregister;
- jtag_alloc;
- jtag_free;

Platform driver on registration with jtag-core creates the next
entry in dev folder:
/dev/jtagX

Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Philippe Ombredanne <pombredanne@nexb.com>
---
v19->v20
Comments pointed by Randy Dunlap <rdunlap@infradead.org>
- Fix JTAG dirver help in Kconfig

Notifications from kbuild test robot <lkp@intel.com>
- fix incompatible type casts

v18->v19
Comments pointed by Julia Cartwright <juliac@eso.teric.us>
- Fix memory leak on jtag_alloc exit

v17->v18
Comments pointed by Julia Cartwright <juliac@eso.teric.us>
- Change to return -EOPNOTSUPP in case of error in JTAG_GIOCFREQ
- Add ops callbacks check to jtag_alloc
- Add err check for copy_to_user
- Move the kfree() above the if (err) in JTAG_IOCXFER
- remove unnecessary check for error after put_user
- add padding to struct jtag_xfer

v16->v17
Comments pointed by Julia Cartwright <juliac@eso.teric.us>
- Fix memory allocation on jtag alloc
- Move out unnecessary form lock on jtag open
- Rework jtag register behavior

v15->v16
Commen ts pointed by Florian Fainelli <f.fainelli@gmail.com>
- move check jtag->ops->* in ioctl before get_user()
- change error type -EINVAL --> -EBUSY on open already opened jtag
- remove unnecessary ARCH_DMA_MINALIGN flag from kzalloc
- remove define ARCH_DMA_MINALIGN

v14->v15
v13->v14
Comments pointed by Philippe Ombredanne <pombredanne@nexb.com>
- Change style of head block comment from /**/ to //

v12->v13
Comments pointed by Philippe Ombredanne <pombredanne@nexb.com>
- Change jtag.c licence type to
  SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
  and reorder line with license in description
v11->v12
Comments pointed by Greg KH <gregkh@linuxfoundation.org>
- Change jtag.h licence type to
  SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
  and reorder line with license in description

Chip Bilbrey <chip@bilbrey.org>
- Remove Apeed reference from uapi jtag.h header
- Remove access mode from xfer and idle transactions
- Add new ioctl JTAG_SIOCMODE for set hw mode
- Add only one open per JTAG port blocking with mutex blocking

v10->v11
Notifications from kbuild test robot <lkp@intel.com>
- include types.h headeri to jtag.h
- fix incompatible type of xfer callback
- remove rdundant class defination
- Fix return order in case of xfer error

V9->v10
Comments pointed by Greg KH <gregkh@linuxfoundation.org>
- remove unnecessary alignment for pirv data
- move jtag_copy_to_user and jtag_copy_from_user code just to ioctl
- move int jtag_run_test_idle_op and jtag_xfer_op code
  just to ioctl
- change return error codes to more applicable
- add missing error checks
- fix error check order in ioctl
- remove unnecessary blank lines
- add param validation to ioctl
- remove compat_ioctl
- remove only one open per JTAG port blocking.
  User will care about this.
- Fix idr memory leak on jtag_exit
- change cdev device type to misc

V8->v9
Comments pointed by Arnd Bergmann <arnd@arndb.de>
- use get_user() instead of __get_user().
- change jtag->open type from int to atomic_t
- remove spinlock on jtg_open
- remove mutex on jtag_register
- add unregister_chrdev_region on jtag_init err
- add unregister_chrdev_region on jtag_exit
- remove unnecessary pointer casts
- add *data parameter to xfer function prototype

v7->v8
Comments pointed by Moritz Fischer <moritz.fischer@ettus.com>
- Fix misspelling s/friver/driver

v6->v7
Notifications from kbuild test robot <lkp@intel.com>
- Remove include asm/types.h from jtag.h
- Add include <linux/types.h> to jtag.c

v5->v6
v4->v5

v3->v4
Comments pointed by Arnd Bergmann <arnd@arndb.de>
- change transaction pointer tdio type  to __u64
- change internal status type from enum to __u32
- reorder jtag_xfer members to avoid the implied padding
- add __packed attribute to jtag_xfer and jtag_run_test_idle

v2->v3
Notifications from kbuild test robot <lkp@intel.com>
- Change include path to <linux/types.h> in jtag.h

v1->v2
Comments pointed by Greg KH <gregkh@linuxfoundation.org>
- Change license type from GPLv2/BSD to GPLv2
- Change type of variables which crossed user/kernel to __type
- Remove "default n" from Kconfig

Comments pointed by Andrew Lunn <andrew@lunn.ch>
- Change list_add_tail in jtag_unregister to list_del

Comments pointed by Neil Armstrong <narmstrong@baylibre.com>
- Add SPDX-License-Identifier instead of license text

Comments pointed by Arnd Bergmann <arnd@arndb.de>
- Change __copy_to_user to memdup_user
- Change __put_user to put_user
- Change type of variables to __type for compatible 32 and 64-bit systems
- Add check for maximum xfer data size
- Change lookup data mechanism to get jtag data from inode
- Add .compat_ioctl to file ops
- Add mem alignment for jtag priv data

Comments pointed by Tobias Klauser <tklauser@distanz.ch>
- Change function names to avoid match with variable types
- Fix description for jtag_ru_test_idle in uapi jtag.h
- Fix misprints IDEL/IDLE, trough/through
---
 Documentation/ioctl/ioctl-number.txt |    2 +
 MAINTAINERS                          |   10 ++
 drivers/Kconfig                      |    2 +
 drivers/Makefile                     |    1 +
 drivers/jtag/Kconfig                 |   18 +++
 drivers/jtag/Makefile                |    1 +
 drivers/jtag/jtag.c                  |  274 ++++++++++++++++++++++++++++++++++
 include/linux/jtag.h                 |   41 +++++
 include/uapi/linux/jtag.h            |  105 +++++++++++++
 9 files changed, 454 insertions(+), 0 deletions(-)
 create mode 100644 drivers/jtag/Kconfig
 create mode 100644 drivers/jtag/Makefile
 create mode 100644 drivers/jtag/jtag.c
 create mode 100644 include/linux/jtag.h
 create mode 100644 include/uapi/linux/jtag.h

diff --git a/Documentation/ioctl/ioctl-number.txt b/Documentation/ioctl/ioctl-number.txt
index 7f7413e..c5548b5 100644
--- a/Documentation/ioctl/ioctl-number.txt
+++ b/Documentation/ioctl/ioctl-number.txt
@@ -318,6 +318,8 @@ Code  Seq#(hex)	Include File		Comments
 0xB0	all	RATIO devices		in development:
 					<mailto:vgo@ratio.de>
 0xB1	00-1F	PPPoX			<mailto:mostrows@styx.uwaterloo.ca>
+0xB2	00-0f	linux/jtag.h		JTAG driver
+					<mailto:oleksandrs@mellanox.com>
 0xB3	00	linux/mmc/ioctl.h
 0xB4	00-0F	linux/gpio.h		<mailto:linux-gpio@vger.kernel.org>
 0xB5	00-0F	uapi/linux/rpmsg.h	<mailto:linux-remoteproc@vger.kernel.org>
diff --git a/MAINTAINERS b/MAINTAINERS
index 79bb02f..2fd22d6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7610,6 +7610,16 @@ L:	linux-serial@vger.kernel.org
 S:	Maintained
 F:	drivers/tty/serial/jsm/
 
+JTAG SUBSYSTEM
+M:	Oleksandr Shamray <oleksandrs@mellanox.com>
+M:	Vadim Pasternak <vadimp@mellanox.com>
+S:	Maintained
+F:	include/linux/jtag.h
+F:	include/uapi/linux/jtag.h
+F:	drivers/jtag/
+F:	Documentation/devicetree/bindings/jtag/
+F:	Documentation/ABI/testing/jtag-cdev
+
 K10TEMP HARDWARE MONITORING DRIVER
 M:	Clemens Ladisch <clemens@ladisch.de>
 L:	linux-hwmon@vger.kernel.org
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 95b9ccc..bb71e48 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -217,4 +217,6 @@ source "drivers/siox/Kconfig"
 
 source "drivers/slimbus/Kconfig"
 
+source "drivers/jtag/Kconfig"
+
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 24cd470..c92636b 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -185,3 +185,4 @@ obj-$(CONFIG_TEE)		+= tee/
 obj-$(CONFIG_MULTIPLEXER)	+= mux/
 obj-$(CONFIG_UNISYS_VISORBUS)	+= visorbus/
 obj-$(CONFIG_SIOX)		+= siox/
+obj-$(CONFIG_JTAG)		+= jtag/
diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig
new file mode 100644
index 0000000..ba72a2b
--- /dev/null
+++ b/drivers/jtag/Kconfig
@@ -0,0 +1,18 @@
+menuconfig JTAG
+	tristate "JTAG support"
+	help
+	  This provides basic core functionality support for JTAG class devices
+	  Hardware that is equipped with a JTAG microcontroller can be
+	  supported by using this driver's interfaces.
+	  This driver exposes a set of IOCTL to the user space for
+	  another commands:
+	  SDR: (Scan Data Register) Performs an IEEE 1149.1 Data Register scan
+	  SIR: (Scan Instruction Register) Performs an IEEE 1149.1 Instruction
+	  Register scan.
+	  RUNTEST: Forces the IEEE 1149.1 bus to a run state for a specified
+	  number of clocks or a specified time period.
+
+	  If you want this support, you should say Y here.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called jtag.
diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile
new file mode 100644
index 0000000..af37493
--- /dev/null
+++ b/drivers/jtag/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_JTAG)		+= jtag.o
diff --git a/drivers/jtag/jtag.c b/drivers/jtag/jtag.c
new file mode 100644
index 0000000..84c29c8
--- /dev/null
+++ b/drivers/jtag/jtag.c
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// drivers/jtag/jtag.c
+//
+// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
+// Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com>
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/jtag.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/rtnetlink.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <uapi/linux/jtag.h>
+
+#define JTAG_NAME	"jtag0"
+#define MAX_JTAG_NAME_LEN (sizeof("jtag") + 5)
+
+struct jtag {
+	struct miscdevice miscdev;
+	struct device *dev;
+	const struct jtag_ops *ops;
+	int id;
+	bool opened;
+	struct mutex open_lock;
+	unsigned long priv[0];
+};
+
+static DEFINE_IDA(jtag_ida);
+
+void *jtag_priv(struct jtag *jtag)
+{
+	return jtag->priv;
+}
+EXPORT_SYMBOL_GPL(jtag_priv);
+
+static long jtag_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct jtag *jtag = file->private_data;
+	struct jtag_run_test_idle idle;
+	struct jtag_xfer xfer;
+	u8 *xfer_data;
+	u32 data_size;
+	u32 value;
+	int err;
+
+	if (!arg)
+		return -EINVAL;
+
+	switch (cmd) {
+	case JTAG_GIOCFREQ:
+		if (!jtag->ops->freq_get)
+			return -EOPNOTSUPP;
+
+		err = jtag->ops->freq_get(jtag, &value);
+		if (err)
+			break;
+
+		if (put_user(value, (__u32 __user *)arg))
+			err = -EFAULT;
+		break;
+
+	case JTAG_SIOCFREQ:
+		if (!jtag->ops->freq_set)
+			return -EOPNOTSUPP;
+
+		if (get_user(value, (__u32 __user *)arg))
+			return -EFAULT;
+		if (value == 0)
+			return -EINVAL;
+
+		err = jtag->ops->freq_set(jtag, value);
+		break;
+
+	case JTAG_IOCRUNTEST:
+		if (copy_from_user(&idle, (const void __user *)arg,
+				   sizeof(struct jtag_run_test_idle)))
+			return -EFAULT;
+
+		if (idle.endstate > JTAG_STATE_PAUSEDR)
+			return -EINVAL;
+
+		err = jtag->ops->idle(jtag, &idle);
+		break;
+
+	case JTAG_IOCXFER:
+		if (copy_from_user(&xfer, (const void __user *)arg,
+				   sizeof(struct jtag_xfer)))
+			return -EFAULT;
+
+		if (xfer.length >= JTAG_MAX_XFER_DATA_LEN)
+			return -EINVAL;
+
+		if (xfer.type > JTAG_SDR_XFER)
+			return -EINVAL;
+
+		if (xfer.direction > JTAG_WRITE_XFER)
+			return -EINVAL;
+
+		if (xfer.endstate > JTAG_STATE_PAUSEDR)
+			return -EINVAL;
+
+		data_size = DIV_ROUND_UP(xfer.length, BITS_PER_BYTE);
+		xfer_data = memdup_user(u64_to_user_ptr(xfer.tdio), data_size);
+
+		if (IS_ERR(xfer_data))
+			return -EFAULT;
+
+		err = jtag->ops->xfer(jtag, &xfer, xfer_data);
+		if (err) {
+			kfree(xfer_data);
+			return -EFAULT;
+		}
+
+		err = copy_to_user(u64_to_user_ptr(xfer.tdio),
+				   (void *)(xfer_data), data_size);
+		kfree(xfer_data);
+		if (err)
+			return -EFAULT;
+
+		if (copy_to_user((void __user *)arg, (void *)&xfer,
+				 sizeof(struct jtag_xfer)))
+			return -EFAULT;
+		break;
+
+	case JTAG_GIOCSTATUS:
+		err = jtag->ops->status_get(jtag, &value);
+		if (err)
+			break;
+
+		err = put_user(value, (__u32 __user *)arg);
+		break;
+	case JTAG_SIOCMODE:
+		if (get_user(value, (__u32 __user *)arg))
+			return -EFAULT;
+		if (value == 0)
+			return -EINVAL;
+
+		err = jtag->ops->mode_set(jtag, value);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return err;
+}
+
+static int jtag_open(struct inode *inode, struct file *file)
+{
+	struct jtag *jtag = container_of(file->private_data, struct jtag,
+					 miscdev);
+
+	if (mutex_lock_interruptible(&jtag->open_lock))
+		return -ERESTARTSYS;
+
+	if (jtag->opened) {
+		mutex_unlock(&jtag->open_lock);
+		return -EBUSY;
+	}
+	jtag->opened = true;
+	mutex_unlock(&jtag->open_lock);
+
+	nonseekable_open(inode, file);
+	file->private_data = jtag;
+	return 0;
+}
+
+static int jtag_release(struct inode *inode, struct file *file)
+{
+	struct jtag *jtag = file->private_data;
+
+	mutex_lock(&jtag->open_lock);
+	jtag->opened = false;
+	mutex_unlock(&jtag->open_lock);
+	return 0;
+}
+
+static const struct file_operations jtag_fops = {
+	.owner		= THIS_MODULE,
+	.open		= jtag_open,
+	.release	= jtag_release,
+	.llseek		= noop_llseek,
+	.unlocked_ioctl = jtag_ioctl,
+};
+
+struct jtag *jtag_alloc(size_t priv_size, const struct jtag_ops *ops)
+{
+	struct jtag *jtag;
+
+	if (!ops)
+		return NULL;
+
+	if (!ops->idle || !ops->mode_set || !ops->status_get || !ops->xfer)
+		return NULL;
+
+	jtag = kzalloc(sizeof(*jtag) + priv_size, GFP_KERNEL);
+	if (!jtag)
+		return NULL;
+
+	jtag->ops = ops;
+	return jtag;
+}
+EXPORT_SYMBOL_GPL(jtag_alloc);
+
+void jtag_free(struct jtag *jtag)
+{
+	kfree(jtag);
+}
+EXPORT_SYMBOL_GPL(jtag_free);
+
+int jtag_register(struct jtag *jtag)
+{
+	char *name;
+	int err;
+	int id;
+
+	id = ida_simple_get(&jtag_ida, 0, 0, GFP_KERNEL);
+	if (id < 0)
+		return id;
+
+	jtag->id = id;
+	jtag->opened = false;
+
+	name = kzalloc(MAX_JTAG_NAME_LEN, GFP_KERNEL);
+	if (!name) {
+		err = -ENOMEM;
+		goto err_jtag_alloc;
+	}
+
+	err = snprintf(name, MAX_JTAG_NAME_LEN, "jtag%d", id);
+	if (err < 0)
+		goto err_jtag_name;
+
+	mutex_init(&jtag->open_lock);
+	jtag->miscdev.fops =  &jtag_fops;
+	jtag->miscdev.minor = MISC_DYNAMIC_MINOR;
+	jtag->miscdev.name = name;
+
+	err = misc_register(&jtag->miscdev);
+	if (err) {
+		dev_err(jtag->dev, "Unable to register device\n");
+		goto err_jtag_name;
+	}
+	return 0;
+
+err_jtag_name:
+	kfree(name);
+err_jtag_alloc:
+	ida_simple_remove(&jtag_ida, id);
+	return err;
+}
+EXPORT_SYMBOL_GPL(jtag_register);
+
+void jtag_unregister(struct jtag *jtag)
+{
+	misc_deregister(&jtag->miscdev);
+	kfree(jtag->miscdev.name);
+	ida_simple_remove(&jtag_ida, jtag->id);
+}
+EXPORT_SYMBOL_GPL(jtag_unregister);
+
+static void __exit jtag_exit(void)
+{
+	ida_destroy(&jtag_ida);
+}
+
+module_exit(jtag_exit);
+
+MODULE_AUTHOR("Oleksandr Shamray <oleksandrs@mellanox.com>");
+MODULE_DESCRIPTION("Generic jtag support");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/jtag.h b/include/linux/jtag.h
new file mode 100644
index 0000000..80a887f
--- /dev/null
+++ b/include/linux/jtag.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0
+// include/linux/jtag.h - JTAG class driver
+//
+// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
+// Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com>
+
+#ifndef __JTAG_H
+#define __JTAG_H
+
+#include <uapi/linux/jtag.h>
+
+#define jtag_u64_to_ptr(arg) ((void *)(uintptr_t)arg)
+
+#define JTAG_MAX_XFER_DATA_LEN 65535
+
+struct jtag;
+/**
+ * struct jtag_ops - callbacks for jtag control functions:
+ *
+ * @freq_get: get frequency function. Filled by device driver
+ * @freq_set: set frequency function. Filled by device driver
+ * @status_get: set status function. Filled by device driver
+ * @idle: set JTAG to idle state function. Filled by device driver
+ * @xfer: send JTAG xfer function. Filled by device driver
+ */
+struct jtag_ops {
+	int (*freq_get)(struct jtag *jtag, u32 *freq);
+	int (*freq_set)(struct jtag *jtag, u32 freq);
+	int (*status_get)(struct jtag *jtag, u32 *state);
+	int (*idle)(struct jtag *jtag, struct jtag_run_test_idle *idle);
+	int (*xfer)(struct jtag *jtag, struct jtag_xfer *xfer, u8 *xfer_data);
+	int (*mode_set)(struct jtag *jtag, u32 mode_mask);
+};
+
+void *jtag_priv(struct jtag *jtag);
+int jtag_register(struct jtag *jtag);
+void jtag_unregister(struct jtag *jtag);
+struct jtag *jtag_alloc(size_t priv_size, const struct jtag_ops *ops);
+void jtag_free(struct jtag *jtag);
+
+#endif /* __JTAG_H */
diff --git a/include/uapi/linux/jtag.h b/include/uapi/linux/jtag.h
new file mode 100644
index 0000000..ecd02a0
--- /dev/null
+++ b/include/uapi/linux/jtag.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0
+// include/uapi/linux/jtag.h - JTAG class driver uapi
+//
+// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
+// Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com>
+
+#ifndef __UAPI_LINUX_JTAG_H
+#define __UAPI_LINUX_JTAG_H
+
+#include <linux/types.h>
+/*
+ * JTAG_XFER_HW_MODE: JTAG hardware mode. Used to set HW drived or bitbang
+ * mode. This is bitmask param of ioctl JTAG_SIOCMODE command
+ */
+#define  JTAG_XFER_HW_MODE 1
+
+/**
+ * enum jtag_endstate:
+ *
+ * @JTAG_STATE_IDLE: JTAG state machine IDLE state
+ * @JTAG_STATE_PAUSEIR: JTAG state machine PAUSE_IR state
+ * @JTAG_STATE_PAUSEDR: JTAG state machine PAUSE_DR state
+ */
+enum jtag_endstate {
+	JTAG_STATE_IDLE,
+	JTAG_STATE_PAUSEIR,
+	JTAG_STATE_PAUSEDR,
+};
+
+/**
+ * enum jtag_xfer_type:
+ *
+ * @JTAG_SIR_XFER: SIR transfer
+ * @JTAG_SDR_XFER: SDR transfer
+ */
+enum jtag_xfer_type {
+	JTAG_SIR_XFER,
+	JTAG_SDR_XFER,
+};
+
+/**
+ * enum jtag_xfer_direction:
+ *
+ * @JTAG_READ_XFER: read transfer
+ * @JTAG_WRITE_XFER: write transfer
+ */
+enum jtag_xfer_direction {
+	JTAG_READ_XFER,
+	JTAG_WRITE_XFER,
+};
+
+/**
+ * struct jtag_run_test_idle - forces JTAG state machine to
+ * RUN_TEST/IDLE state
+ *
+ * @reset: 0 - run IDLE/PAUSE from current state
+ *         1 - go through TEST_LOGIC/RESET state before  IDLE/PAUSE
+ * @end: completion flag
+ * @tck: clock counter
+ *
+ * Structure represents interface to JTAG device for jtag idle
+ * execution.
+ */
+struct jtag_run_test_idle {
+	__u8	reset;
+	__u8	endstate;
+	__u8	tck;
+};
+
+/**
+ * struct jtag_xfer - jtag xfer:
+ *
+ * @type: transfer type
+ * @direction: xfer direction
+ * @length: xfer bits len
+ * @tdio : xfer data array
+ * @endir: xfer end state
+ *
+ * Structure represents interface to JTAG device for jtag sdr xfer
+ * execution.
+ */
+struct jtag_xfer {
+	__u8	type;
+	__u8	direction;
+	__u8	endstate;
+	__u8	padding;
+	__u32	length;
+	__u64	tdio;
+};
+
+/* ioctl interface */
+#define __JTAG_IOCTL_MAGIC	0xb2
+
+#define JTAG_IOCRUNTEST	_IOW(__JTAG_IOCTL_MAGIC, 0,\
+			     struct jtag_run_test_idle)
+#define JTAG_SIOCFREQ	_IOW(__JTAG_IOCTL_MAGIC, 1, unsigned int)
+#define JTAG_GIOCFREQ	_IOR(__JTAG_IOCTL_MAGIC, 2, unsigned int)
+#define JTAG_IOCXFER	_IOWR(__JTAG_IOCTL_MAGIC, 3, struct jtag_xfer)
+#define JTAG_GIOCSTATUS _IOWR(__JTAG_IOCTL_MAGIC, 4, enum jtag_endstate)
+#define JTAG_SIOCMODE	_IOW(__JTAG_IOCTL_MAGIC, 5, unsigned int)
+
+#define JTAG_FIRST_MINOR 0
+#define JTAG_MAX_DEVICES 32
+
+#endif /* __UAPI_LINUX_JTAG_H */
-- 
1.7.1

^ permalink raw reply related

* [patch v20 2/4] drivers: jtag: Add Aspeed SoC 24xx and 25xx families JTAG master driver
From: Oleksandr Shamray @ 2018-05-14 16:20 UTC (permalink / raw)
  To: gregkh, arnd
  Cc: linux-kernel, linux-arm-kernel, devicetree, openbmc, joel, jiri,
	tklauser, linux-serial, vadimp, system-sw-low-level, robh+dt,
	openocd-devel-owner, linux-api, davem, mchehab, Oleksandr Shamray,
	Jiri Pirko
In-Reply-To: <1526314803-3532-1-git-send-email-oleksandrs@mellanox.com>

Driver adds support of Aspeed 2500/2400 series SOC JTAG master controller.

Driver implements the following jtag ops:
- freq_get;
- freq_set;
- status_get;
- idle;
- xfer;

It has been tested on Mellanox system with BMC equipped with
Aspeed 2520 SoC for programming CPLD devices.

Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
Acked-by: Philippe Ombredanne <pombredanne@nexb.com>
Acked-by: Joel Stanley <joel@jms.id.au>
---
v19->v20
Notifications from kbuild test robot <lkp@intel.com>
- add static declaration to 'aspeed_jtag_init' and
  'aspeed_jtag_deinit' functions

v18->v19
v17->v18
v16->v17
v15->v16
Comments pointed by Joel Stanley <joel.stan@gmail.com>
- Add reset_control on Jtag init/deinit

v14->v15
Comments pointed by Joel Stanley <joel.stan@gmail.com>
- Add ARCH_ASPEED || COMPILE_TEST to Kconfig
- remove unused offset variable
- remove "aspeed_jtag" from dev_err and dev_dbg messages
- change clk_prepare_enable initialisation order

v13->v14
Comments pointed by Philippe Ombredanne <pombredanne@nexb.com>
- Change style of head block comment from /**/ to //

v12->v13
Comments pointed by Philippe Ombredanne <pombredanne@nexb.com>
- Change jtag-aspeed.c licence type to
  SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
  and reorder line with license in description
Comments pointed by Kun Yi <kunyi@google.com>
- Changed capability check for aspeed,ast2400-jtag/ast200-jtag

v11->v12
Comments pointed by Chip Bilbrey <chip@bilbrey.org>
- Remove access mode from xfer and idle transactions
- Add new ioctl JTAG_SIOCMODE for set hw mode

v10->v11
v9->v10
V8->v9
Comments pointed by Arnd Bergmann <arnd@arndb.de>
- add *data parameter to xfer function prototype

v7->v8
Comments pointed by Joel Stanley <joel.stan@gmail.com>
- aspeed_jtag_init replace goto to return;
- change input variables type from __u32 to u32
  in functios freq_get, freq_set, status_get
- change sm_ variables type from char to u8
- in jatg_init add disable clocks on error case
- remove release_mem_region on error case
- remove devm_free_irq on jtag_deinit
- Fix misspelling Disabe/Disable
- Change compatible string to ast2400 and ast2000

v6->v7
Notifications from kbuild test robot <lkp@intel.com>
- Add include <linux/types.h> to jtag-asapeed.c

v5->v6
v4->v5
Comments pointed by Arnd Bergmann <arnd@arndb.de>
- Added HAS_IOMEM dependence in Kconfig to avoid
  "undefined reference to `devm_ioremap_resource'" error,
  because in some arch this not supported

v3->v4
Comments pointed by Arnd Bergmann <arnd@arndb.de>
- change transaction pointer tdio type  to __u64
- change internal status type from enum to __u32

v2->v3

v1->v2
Comments pointed by Greg KH <gregkh@linuxfoundation.org>
- change license type from GPLv2/BSD to GPLv2

Comments pointed by Neil Armstrong <narmstrong@baylibre.com>
- Add clk_prepare_enable/clk_disable_unprepare in clock init/deinit
- Change .compatible to soc-specific compatible names
  aspeed,aspeed4000-jtag/aspeed5000-jtag
- Added dt-bindings

Comments pointed by Arnd Bergmann <arnd@arndb.de>
- Reorder functions and removed the forward declarations
- Add static const qualifier to state machine states transitions
- Change .compatible to soc-specific compatible names
  aspeed,aspeed4000-jtag/aspeed5000-jtag
- Add dt-bindings

Comments pointed by Randy Dunlap <rdunlap@infradead.org>
- Change module name jtag-aspeed in description in Kconfig

Comments pointed by kbuild test robot <lkp@intel.com>
- Remove invalid include <asm/mach-types.h>
- add resource_size instead of calculation
---
 drivers/jtag/Kconfig       |   14 +
 drivers/jtag/Makefile      |    1 +
 drivers/jtag/jtag-aspeed.c |  786 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 801 insertions(+), 0 deletions(-)
 create mode 100644 drivers/jtag/jtag-aspeed.c

diff --git a/drivers/jtag/Kconfig b/drivers/jtag/Kconfig
index ba72a2b..07950a6 100644
--- a/drivers/jtag/Kconfig
+++ b/drivers/jtag/Kconfig
@@ -16,3 +16,17 @@ menuconfig JTAG
 
 	  To compile this driver as a module, choose M here: the module will
 	  be called jtag.
+
+menuconfig JTAG_ASPEED
+	tristate "Aspeed SoC JTAG controller support"
+	depends on JTAG && HAS_IOMEM
+	depends on ARCH_ASPEED || COMPILE_TEST
+	help
+	  This provides a support for Aspeed JTAG device, equipped on
+	  Aspeed SoC 24xx and 25xx families. Drivers allows programming
+	  of hardware devices, connected to SoC through the JTAG interface.
+
+	  If you want this support, you should say Y here.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called jtag-aspeed.
diff --git a/drivers/jtag/Makefile b/drivers/jtag/Makefile
index af37493..04a855e 100644
--- a/drivers/jtag/Makefile
+++ b/drivers/jtag/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_JTAG)		+= jtag.o
+obj-$(CONFIG_JTAG_ASPEED)	+= jtag-aspeed.o
diff --git a/drivers/jtag/jtag-aspeed.c b/drivers/jtag/jtag-aspeed.c
new file mode 100644
index 0000000..2d22de0
--- /dev/null
+++ b/drivers/jtag/jtag-aspeed.c
@@ -0,0 +1,786 @@
+// SPDX-License-Identifier: GPL-2.0
+// drivers/jtag/aspeed-jtag.c
+//
+// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
+// Copyright (c) 2018 Oleksandr Shamray <oleksandrs@mellanox.com>
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/jtag.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <uapi/linux/jtag.h>
+
+#define ASPEED_JTAG_DATA		0x00
+#define ASPEED_JTAG_INST		0x04
+#define ASPEED_JTAG_CTRL		0x08
+#define ASPEED_JTAG_ISR			0x0C
+#define ASPEED_JTAG_SW			0x10
+#define ASPEED_JTAG_TCK			0x14
+#define ASPEED_JTAG_EC			0x18
+
+#define ASPEED_JTAG_DATA_MSB		0x01
+#define ASPEED_JTAG_DATA_CHUNK_SIZE	0x20
+
+/* ASPEED_JTAG_CTRL: Engine Control */
+#define ASPEED_JTAG_CTL_ENG_EN		BIT(31)
+#define ASPEED_JTAG_CTL_ENG_OUT_EN	BIT(30)
+#define ASPEED_JTAG_CTL_FORCE_TMS	BIT(29)
+#define ASPEED_JTAG_CTL_INST_LEN(x)	((x) << 20)
+#define ASPEED_JTAG_CTL_LASPEED_INST	BIT(17)
+#define ASPEED_JTAG_CTL_INST_EN		BIT(16)
+#define ASPEED_JTAG_CTL_DR_UPDATE	BIT(10)
+#define ASPEED_JTAG_CTL_DATA_LEN(x)	((x) << 4)
+#define ASPEED_JTAG_CTL_LASPEED_DATA	BIT(1)
+#define ASPEED_JTAG_CTL_DATA_EN		BIT(0)
+
+/* ASPEED_JTAG_ISR : Interrupt status and enable */
+#define ASPEED_JTAG_ISR_INST_PAUSE	BIT(19)
+#define ASPEED_JTAG_ISR_INST_COMPLETE	BIT(18)
+#define ASPEED_JTAG_ISR_DATA_PAUSE	BIT(17)
+#define ASPEED_JTAG_ISR_DATA_COMPLETE	BIT(16)
+#define ASPEED_JTAG_ISR_INST_PAUSE_EN	BIT(3)
+#define ASPEED_JTAG_ISR_INST_COMPLETE_EN BIT(2)
+#define ASPEED_JTAG_ISR_DATA_PAUSE_EN	BIT(1)
+#define ASPEED_JTAG_ISR_DATA_COMPLETE_EN BIT(0)
+#define ASPEED_JTAG_ISR_INT_EN_MASK	GENMASK(3, 0)
+#define ASPEED_JTAG_ISR_INT_MASK	GENMASK(19, 16)
+
+/* ASPEED_JTAG_SW : Software Mode and Status */
+#define ASPEED_JTAG_SW_MODE_EN		BIT(19)
+#define ASPEED_JTAG_SW_MODE_TCK		BIT(18)
+#define ASPEED_JTAG_SW_MODE_TMS		BIT(17)
+#define ASPEED_JTAG_SW_MODE_TDIO	BIT(16)
+
+/* ASPEED_JTAG_TCK : TCK Control */
+#define ASPEED_JTAG_TCK_DIVISOR_MASK	GENMASK(10, 0)
+#define ASPEED_JTAG_TCK_GET_DIV(x)	((x) & ASPEED_JTAG_TCK_DIVISOR_MASK)
+
+/* ASPEED_JTAG_EC : Controller set for go to IDLE */
+#define ASPEED_JTAG_EC_GO_IDLE		BIT(0)
+
+#define ASPEED_JTAG_IOUT_LEN(len)	(ASPEED_JTAG_CTL_ENG_EN |\
+					 ASPEED_JTAG_CTL_ENG_OUT_EN |\
+					 ASPEED_JTAG_CTL_INST_LEN(len))
+
+#define ASPEED_JTAG_DOUT_LEN(len)	(ASPEED_JTAG_CTL_ENG_EN |\
+					 ASPEED_JTAG_CTL_ENG_OUT_EN |\
+					 ASPEED_JTAG_CTL_DATA_LEN(len))
+
+#define ASPEED_JTAG_TCK_WAIT		10
+#define ASPEED_JTAG_RESET_CNTR		10
+
+#define ASPEED_JTAG_NAME		"jtag-aspeed"
+
+struct aspeed_jtag {
+	void __iomem			*reg_base;
+	struct device			*dev;
+	struct clk			*pclk;
+	enum jtag_endstate		status;
+	int				irq;
+	struct reset_control		*rst;
+	u32				flag;
+	wait_queue_head_t		jtag_wq;
+	u32				mode;
+};
+
+static char *end_status_str[] = {"idle", "ir pause", "drpause"};
+
+static u32 aspeed_jtag_read(struct aspeed_jtag *aspeed_jtag, u32 reg)
+{
+	return readl(aspeed_jtag->reg_base + reg);
+}
+
+static void
+aspeed_jtag_write(struct aspeed_jtag *aspeed_jtag, u32 val, u32 reg)
+{
+	writel(val, aspeed_jtag->reg_base + reg);
+}
+
+static int aspeed_jtag_freq_set(struct jtag *jtag, u32 freq)
+{
+	struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag);
+	unsigned long apb_frq;
+	u32 tck_val;
+	u16 div;
+
+	apb_frq = clk_get_rate(aspeed_jtag->pclk);
+	div = (apb_frq % freq == 0) ? (apb_frq / freq) - 1 : (apb_frq / freq);
+	tck_val = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK);
+	aspeed_jtag_write(aspeed_jtag,
+			  (tck_val & ASPEED_JTAG_TCK_DIVISOR_MASK) | div,
+			  ASPEED_JTAG_TCK);
+	return 0;
+}
+
+static int aspeed_jtag_freq_get(struct jtag *jtag, u32 *frq)
+{
+	struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag);
+	u32 pclk;
+	u32 tck;
+
+	pclk = clk_get_rate(aspeed_jtag->pclk);
+	tck = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_TCK);
+	*frq = pclk / (ASPEED_JTAG_TCK_GET_DIV(tck) + 1);
+
+	return 0;
+}
+
+static int aspeed_jtag_mode_set(struct jtag *jtag, u32 mode)
+{
+	struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag);
+
+	aspeed_jtag->mode = mode;
+	return 0;
+}
+
+static void aspeed_jtag_sw_delay(struct aspeed_jtag *aspeed_jtag, int cnt)
+{
+	int i;
+
+	for (i = 0; i < cnt; i++)
+		aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW);
+}
+
+static char aspeed_jtag_tck_cycle(struct aspeed_jtag *aspeed_jtag,
+				  u8 tms, u8 tdi)
+{
+	char tdo = 0;
+
+	/* TCK = 0 */
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+			  (tms * ASPEED_JTAG_SW_MODE_TMS) |
+			  (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW);
+
+	aspeed_jtag_sw_delay(aspeed_jtag, ASPEED_JTAG_TCK_WAIT);
+
+	/* TCK = 1 */
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+			  ASPEED_JTAG_SW_MODE_TCK |
+			  (tms * ASPEED_JTAG_SW_MODE_TMS) |
+			  (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW);
+
+	if (aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_SW) &
+	    ASPEED_JTAG_SW_MODE_TDIO)
+		tdo = 1;
+
+	aspeed_jtag_sw_delay(aspeed_jtag, ASPEED_JTAG_TCK_WAIT);
+
+	/* TCK = 0 */
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+			  (tms * ASPEED_JTAG_SW_MODE_TMS) |
+			  (tdi * ASPEED_JTAG_SW_MODE_TDIO), ASPEED_JTAG_SW);
+	return tdo;
+}
+
+static void aspeed_jtag_wait_instruction_pause(struct aspeed_jtag *aspeed_jtag)
+{
+	wait_event_interruptible(aspeed_jtag->jtag_wq, aspeed_jtag->flag &
+				 ASPEED_JTAG_ISR_INST_PAUSE);
+	aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_PAUSE;
+}
+
+static void
+aspeed_jtag_wait_instruction_complete(struct aspeed_jtag *aspeed_jtag)
+{
+	wait_event_interruptible(aspeed_jtag->jtag_wq, aspeed_jtag->flag &
+				 ASPEED_JTAG_ISR_INST_COMPLETE);
+	aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_INST_COMPLETE;
+}
+
+static void
+aspeed_jtag_wait_data_pause_complete(struct aspeed_jtag *aspeed_jtag)
+{
+	wait_event_interruptible(aspeed_jtag->jtag_wq, aspeed_jtag->flag &
+				 ASPEED_JTAG_ISR_DATA_PAUSE);
+	aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_PAUSE;
+}
+
+static void aspeed_jtag_wait_data_complete(struct aspeed_jtag *aspeed_jtag)
+{
+	wait_event_interruptible(aspeed_jtag->jtag_wq, aspeed_jtag->flag &
+				 ASPEED_JTAG_ISR_DATA_COMPLETE);
+	aspeed_jtag->flag &= ~ASPEED_JTAG_ISR_DATA_COMPLETE;
+}
+
+static void aspeed_jtag_sm_cycle(struct aspeed_jtag *aspeed_jtag, const u8 *tms,
+				 int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++)
+		aspeed_jtag_tck_cycle(aspeed_jtag, tms[i], 0);
+}
+
+static void aspeed_jtag_run_test_idle_sw(struct aspeed_jtag *aspeed_jtag,
+					 struct jtag_run_test_idle *runtest)
+{
+	static const u8 sm_pause_irpause[] = {1, 1, 1, 1, 0, 1, 0};
+	static const u8 sm_pause_drpause[] = {1, 1, 1, 0, 1, 0};
+	static const u8 sm_idle_irpause[] = {1, 1, 0, 1, 0};
+	static const u8 sm_idle_drpause[] = {1, 0, 1, 0};
+	static const u8 sm_pause_idle[] = {1, 1, 0};
+	int i;
+
+	/* SW mode from idle/pause-> to pause/idle */
+	if (runtest->reset) {
+		for (i = 0; i < ASPEED_JTAG_RESET_CNTR; i++)
+			aspeed_jtag_tck_cycle(aspeed_jtag, 1, 0);
+	}
+
+	switch (aspeed_jtag->status) {
+	case JTAG_STATE_IDLE:
+		switch (runtest->endstate) {
+		case JTAG_STATE_PAUSEIR:
+			/* ->DRSCan->IRSCan->IRCap->IRExit1->PauseIR */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_idle_irpause,
+					     sizeof(sm_idle_irpause));
+
+			aspeed_jtag->status = JTAG_STATE_PAUSEIR;
+			break;
+		case JTAG_STATE_PAUSEDR:
+			/* ->DRSCan->DRCap->DRExit1->PauseDR */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_idle_drpause,
+					     sizeof(sm_idle_drpause));
+
+			aspeed_jtag->status = JTAG_STATE_PAUSEDR;
+			break;
+		case JTAG_STATE_IDLE:
+			/* IDLE */
+			aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0);
+			aspeed_jtag->status = JTAG_STATE_IDLE;
+			break;
+		default:
+			break;
+		}
+		break;
+
+	case JTAG_STATE_PAUSEIR:
+	/* Fall-through */
+	case JTAG_STATE_PAUSEDR:
+		/* From IR/DR Pause */
+		switch (runtest->endstate) {
+		case JTAG_STATE_PAUSEIR:
+			/*
+			 * to Exit2 IR/DR->Updt IR/DR->DRSCan->IRSCan->IRCap->
+			 * IRExit1->PauseIR
+			 */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_irpause,
+					     sizeof(sm_pause_irpause));
+
+			aspeed_jtag->status = JTAG_STATE_PAUSEIR;
+			break;
+		case JTAG_STATE_PAUSEDR:
+			/*
+			 * to Exit2 IR/DR->Updt IR/DR->DRSCan->DRCap->
+			 * DRExit1->PauseDR
+			 */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_drpause,
+					     sizeof(sm_pause_drpause));
+			aspeed_jtag->status = JTAG_STATE_PAUSEDR;
+			break;
+		case JTAG_STATE_IDLE:
+			/* to Exit2 IR/DR->Updt IR/DR->IDLE */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_idle,
+					     sizeof(sm_pause_idle));
+			aspeed_jtag->status = JTAG_STATE_IDLE;
+			break;
+		default:
+			break;
+		}
+		break;
+
+	default:
+		dev_err(aspeed_jtag->dev, "aspeed_jtag_run_test_idle error\n");
+		break;
+	}
+
+	/* Stay on IDLE for at least  TCK cycle */
+	for (i = 0; i < runtest->tck; i++)
+		aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0);
+}
+
+/**
+ * aspeed_jtag_run_test_idle:
+ * JTAG reset: generates at least 9 TMS high and 1 TMS low to force
+ * devices into Run-Test/Idle State.
+ */
+static int aspeed_jtag_idle(struct jtag *jtag,
+			    struct jtag_run_test_idle *runtest)
+{
+	struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag);
+
+	dev_dbg(aspeed_jtag->dev, "runtest, status:%d, mode:%s, state:%s, reset:%d, tck:%d\n",
+		aspeed_jtag->status,
+		aspeed_jtag->mode & JTAG_XFER_HW_MODE ? "HW" : "SW",
+		end_status_str[runtest->endstate], runtest->reset,
+		runtest->tck);
+
+	if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) {
+		aspeed_jtag_run_test_idle_sw(aspeed_jtag, runtest);
+		return 0;
+	}
+
+	/* Disable sw mode */
+	aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW);
+	/* x TMS high + 1 TMS low */
+	if (runtest->reset)
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_CTL_ENG_EN |
+				  ASPEED_JTAG_CTL_ENG_OUT_EN |
+				  ASPEED_JTAG_CTL_FORCE_TMS, ASPEED_JTAG_CTRL);
+	else
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_EC_GO_IDLE,
+				  ASPEED_JTAG_EC);
+
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+			  ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW);
+
+	aspeed_jtag->status = JTAG_STATE_IDLE;
+	return 0;
+}
+
+static void aspeed_jtag_xfer_sw(struct aspeed_jtag *aspeed_jtag,
+				struct jtag_xfer *xfer, unsigned long *data)
+{
+	unsigned long remain_xfer = xfer->length;
+	unsigned long shift_bits = 0;
+	unsigned long index = 0;
+	unsigned long tdi;
+	char          tdo;
+
+	if (xfer->direction == JTAG_READ_XFER)
+		tdi = UINT_MAX;
+	else
+		tdi = data[index];
+
+	while (remain_xfer > 1) {
+		tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 0,
+					    tdi & ASPEED_JTAG_DATA_MSB);
+		data[index] |= tdo << (shift_bits %
+					    ASPEED_JTAG_DATA_CHUNK_SIZE);
+
+		tdi >>= 1;
+		shift_bits++;
+		remain_xfer--;
+
+		if (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE == 0) {
+			dev_dbg(aspeed_jtag->dev, "R/W data[%lu]:%lx\n",
+				index, data[index]);
+
+			tdo = 0;
+			index++;
+
+			if (xfer->direction == JTAG_READ_XFER)
+				tdi = UINT_MAX;
+			else
+				tdi = data[index];
+		}
+	}
+
+	tdo = aspeed_jtag_tck_cycle(aspeed_jtag, 1, tdi & ASPEED_JTAG_DATA_MSB);
+	data[index] |= tdo << (shift_bits % ASPEED_JTAG_DATA_CHUNK_SIZE);
+}
+
+static void aspeed_jtag_xfer_push_data(struct aspeed_jtag *aspeed_jtag,
+				       enum jtag_xfer_type type, u32 bits_len)
+{
+	dev_dbg(aspeed_jtag->dev, "shift bits %d\n", bits_len);
+
+	if (type == JTAG_SIR_XFER) {
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_IOUT_LEN(bits_len),
+				  ASPEED_JTAG_CTRL);
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len) |
+				  ASPEED_JTAG_CTL_INST_EN, ASPEED_JTAG_CTRL);
+	} else {
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len),
+				  ASPEED_JTAG_CTRL);
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_DOUT_LEN(bits_len) |
+				  ASPEED_JTAG_CTL_DATA_EN, ASPEED_JTAG_CTRL);
+	}
+}
+
+static void aspeed_jtag_xfer_push_data_last(struct aspeed_jtag *aspeed_jtag,
+					    enum jtag_xfer_type type,
+					    u32 shift_bits,
+					    enum jtag_endstate endstate)
+{
+	if (endstate != JTAG_STATE_IDLE) {
+		if (type == JTAG_SIR_XFER) {
+			dev_dbg(aspeed_jtag->dev, "IR Keep Pause\n");
+
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_IOUT_LEN(shift_bits),
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_IOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_INST_EN,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_wait_instruction_pause(aspeed_jtag);
+		} else {
+			dev_dbg(aspeed_jtag->dev, "DR Keep Pause\n");
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_DOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_DR_UPDATE,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_DOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_DR_UPDATE |
+					  ASPEED_JTAG_CTL_DATA_EN,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_wait_data_pause_complete(aspeed_jtag);
+		}
+	} else {
+		if (type == JTAG_SIR_XFER) {
+			dev_dbg(aspeed_jtag->dev, "IR go IDLE\n");
+
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_IOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_LASPEED_INST,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_IOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_LASPEED_INST |
+					  ASPEED_JTAG_CTL_INST_EN,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_wait_instruction_complete(aspeed_jtag);
+		} else {
+			dev_dbg(aspeed_jtag->dev, "DR go IDLE\n");
+
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_DOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_LASPEED_DATA,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_write(aspeed_jtag,
+					  ASPEED_JTAG_DOUT_LEN(shift_bits) |
+					  ASPEED_JTAG_CTL_LASPEED_DATA |
+					  ASPEED_JTAG_CTL_DATA_EN,
+					  ASPEED_JTAG_CTRL);
+			aspeed_jtag_wait_data_complete(aspeed_jtag);
+		}
+	}
+}
+
+static void aspeed_jtag_xfer_hw(struct aspeed_jtag *aspeed_jtag,
+				struct jtag_xfer *xfer, unsigned long *data)
+{
+	unsigned long remain_xfer = xfer->length;
+	unsigned long index = 0;
+	char shift_bits;
+	u32 data_reg;
+
+	data_reg = xfer->type == JTAG_SIR_XFER ?
+		   ASPEED_JTAG_INST : ASPEED_JTAG_DATA;
+	while (remain_xfer) {
+		if (xfer->direction == JTAG_WRITE_XFER) {
+			dev_dbg(aspeed_jtag->dev, "W dr->dr_data[%lu]:%lx\n",
+				index, data[index]);
+
+			aspeed_jtag_write(aspeed_jtag, data[index], data_reg);
+		} else {
+			aspeed_jtag_write(aspeed_jtag, 0, data_reg);
+		}
+
+		if (remain_xfer > ASPEED_JTAG_DATA_CHUNK_SIZE) {
+			shift_bits = ASPEED_JTAG_DATA_CHUNK_SIZE;
+
+			/*
+			 * Read bytes were not equals to column length
+			 * and go to Pause-DR
+			 */
+			aspeed_jtag_xfer_push_data(aspeed_jtag, xfer->type,
+						   shift_bits);
+		} else {
+			/*
+			 * Read bytes equals to column length =>
+			 * Update-DR
+			 */
+			shift_bits = remain_xfer;
+			aspeed_jtag_xfer_push_data_last(aspeed_jtag, xfer->type,
+							shift_bits,
+							xfer->endstate);
+		}
+
+		if (xfer->direction == JTAG_READ_XFER) {
+			if (shift_bits < ASPEED_JTAG_DATA_CHUNK_SIZE) {
+				data[index] = aspeed_jtag_read(aspeed_jtag,
+							       data_reg);
+
+				data[index] >>= ASPEED_JTAG_DATA_CHUNK_SIZE -
+								shift_bits;
+			} else {
+				data[index] = aspeed_jtag_read(aspeed_jtag,
+							       data_reg);
+			}
+			dev_dbg(aspeed_jtag->dev, "R dr->dr_data[%lu]:%lx\n",
+				index, data[index]);
+		}
+
+		remain_xfer = remain_xfer - shift_bits;
+		index++;
+		dev_dbg(aspeed_jtag->dev, "remain_xfer %lu\n", remain_xfer);
+	}
+}
+
+static int aspeed_jtag_xfer(struct jtag *jtag, struct jtag_xfer *xfer,
+			    u8 *xfer_data)
+{
+	static const u8 sm_update_shiftir[] = {1, 1, 0, 0};
+	static const u8 sm_update_shiftdr[] = {1, 0, 0};
+	static const u8 sm_pause_idle[] = {1, 1, 0};
+	static const u8 sm_pause_update[] = {1, 1};
+	struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag);
+	unsigned long *data = (unsigned long *)xfer_data;
+	unsigned long remain_xfer = xfer->length;
+	char dbg_str[256];
+	int pos = 0;
+	int i;
+
+	for (i = 0; i <= xfer->length / BITS_PER_BYTE; i++) {
+		pos += snprintf(&dbg_str[pos], sizeof(dbg_str) - pos,
+				"0x%02x ", xfer_data[i]);
+	}
+
+	dev_dbg(aspeed_jtag->dev, " %s %s xfer, mode:%s, END:%d, len:%lu, TDI[%s]\n",
+		xfer->type == JTAG_SIR_XFER ? "SIR" : "SDR",
+		xfer->direction == JTAG_READ_XFER ? "READ" : "WRITE",
+		aspeed_jtag->mode & JTAG_XFER_HW_MODE ? "HW" : "SW",
+		xfer->endstate, remain_xfer, dbg_str);
+
+	if (!(aspeed_jtag->mode & JTAG_XFER_HW_MODE)) {
+		/* SW mode */
+		aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+				  ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW);
+
+		if (aspeed_jtag->status != JTAG_STATE_IDLE) {
+			/*IR/DR Pause->Exit2 IR / DR->Update IR /DR */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_update,
+					     sizeof(sm_pause_update));
+		}
+
+		if (xfer->type == JTAG_SIR_XFER)
+			/* ->IRSCan->CapIR->ShiftIR */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_update_shiftir,
+					     sizeof(sm_update_shiftir));
+		else
+			/* ->DRScan->DRCap->DRShift */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_update_shiftdr,
+					     sizeof(sm_update_shiftdr));
+
+		aspeed_jtag_xfer_sw(aspeed_jtag, xfer, data);
+
+		/* DIPause/DRPause */
+		aspeed_jtag_tck_cycle(aspeed_jtag, 0, 0);
+
+		if (xfer->endstate == JTAG_STATE_IDLE) {
+			/* ->DRExit2->DRUpdate->IDLE */
+			aspeed_jtag_sm_cycle(aspeed_jtag, sm_pause_idle,
+					     sizeof(sm_pause_idle));
+		}
+	} else {
+		/* hw mode */
+		aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_SW);
+		aspeed_jtag_xfer_hw(aspeed_jtag, xfer, data);
+	}
+
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+			  ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW);
+	aspeed_jtag->status = xfer->endstate;
+	return 0;
+}
+
+static int aspeed_jtag_status_get(struct jtag *jtag, u32 *status)
+{
+	struct aspeed_jtag *aspeed_jtag = jtag_priv(jtag);
+
+	*status = aspeed_jtag->status;
+	return 0;
+}
+
+static irqreturn_t aspeed_jtag_interrupt(s32 this_irq, void *dev_id)
+{
+	struct aspeed_jtag *aspeed_jtag = dev_id;
+	irqreturn_t ret;
+	u32 status;
+
+	status = aspeed_jtag_read(aspeed_jtag, ASPEED_JTAG_ISR);
+	dev_dbg(aspeed_jtag->dev, "status %x\n", status);
+
+	if (status & ASPEED_JTAG_ISR_INT_MASK) {
+		aspeed_jtag_write(aspeed_jtag,
+				  (status & ASPEED_JTAG_ISR_INT_MASK)
+				  | (status & ASPEED_JTAG_ISR_INT_EN_MASK),
+				  ASPEED_JTAG_ISR);
+		aspeed_jtag->flag |= status & ASPEED_JTAG_ISR_INT_MASK;
+	}
+
+	if (aspeed_jtag->flag) {
+		wake_up_interruptible(&aspeed_jtag->jtag_wq);
+		ret = IRQ_HANDLED;
+	} else {
+		dev_err(aspeed_jtag->dev, "irq status:%x\n",
+			status);
+		ret = IRQ_NONE;
+	}
+	return ret;
+}
+
+static int aspeed_jtag_init(struct platform_device *pdev,
+			    struct aspeed_jtag *aspeed_jtag)
+{
+	struct resource *res;
+	int err;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	aspeed_jtag->reg_base = devm_ioremap_resource(aspeed_jtag->dev, res);
+	if (IS_ERR(aspeed_jtag->reg_base))
+		return -ENOMEM;
+
+	aspeed_jtag->pclk = devm_clk_get(aspeed_jtag->dev, NULL);
+	if (IS_ERR(aspeed_jtag->pclk)) {
+		dev_err(aspeed_jtag->dev, "devm_clk_get failed\n");
+		return PTR_ERR(aspeed_jtag->pclk);
+	}
+
+	aspeed_jtag->irq = platform_get_irq(pdev, 0);
+	if (aspeed_jtag->irq < 0) {
+		dev_err(aspeed_jtag->dev, "no irq specified\n");
+		return -ENOENT;
+	}
+
+	clk_prepare_enable(aspeed_jtag->pclk);
+
+	aspeed_jtag->rst = devm_reset_control_get_shared(aspeed_jtag->dev,
+							 NULL);
+	if (IS_ERR(aspeed_jtag->rst)) {
+		dev_err(aspeed_jtag->dev,
+			"missing or invalid reset controller device tree entry");
+		return PTR_ERR(aspeed_jtag->rst);
+	}
+	reset_control_deassert(aspeed_jtag->rst);
+
+	/* Enable clock */
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_CTL_ENG_EN |
+			  ASPEED_JTAG_CTL_ENG_OUT_EN, ASPEED_JTAG_CTRL);
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_SW_MODE_EN |
+			  ASPEED_JTAG_SW_MODE_TDIO, ASPEED_JTAG_SW);
+
+	err = devm_request_irq(aspeed_jtag->dev, aspeed_jtag->irq,
+			       aspeed_jtag_interrupt, 0,
+			       "aspeed-jtag", aspeed_jtag);
+	if (err) {
+		dev_err(aspeed_jtag->dev, "unable to get IRQ");
+		goto clk_unprep;
+	}
+	dev_dbg(&pdev->dev, "IRQ %d.\n", aspeed_jtag->irq);
+
+	aspeed_jtag_write(aspeed_jtag, ASPEED_JTAG_ISR_INST_PAUSE |
+			  ASPEED_JTAG_ISR_INST_COMPLETE |
+			  ASPEED_JTAG_ISR_DATA_PAUSE |
+			  ASPEED_JTAG_ISR_DATA_COMPLETE |
+			  ASPEED_JTAG_ISR_INST_PAUSE_EN |
+			  ASPEED_JTAG_ISR_INST_COMPLETE_EN |
+			  ASPEED_JTAG_ISR_DATA_PAUSE_EN |
+			  ASPEED_JTAG_ISR_DATA_COMPLETE_EN,
+			  ASPEED_JTAG_ISR);
+
+	aspeed_jtag->flag = 0;
+	aspeed_jtag->mode = 0;
+	init_waitqueue_head(&aspeed_jtag->jtag_wq);
+	return 0;
+
+clk_unprep:
+	clk_disable_unprepare(aspeed_jtag->pclk);
+	return err;
+}
+
+static int aspeed_jtag_deinit(struct platform_device *pdev,
+			      struct aspeed_jtag *aspeed_jtag)
+{
+	aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_ISR);
+	/* Disable clock */
+	aspeed_jtag_write(aspeed_jtag, 0, ASPEED_JTAG_CTRL);
+	reset_control_assert(aspeed_jtag->rst);
+	clk_disable_unprepare(aspeed_jtag->pclk);
+	return 0;
+}
+
+static const struct jtag_ops aspeed_jtag_ops = {
+	.freq_get = aspeed_jtag_freq_get,
+	.freq_set = aspeed_jtag_freq_set,
+	.status_get = aspeed_jtag_status_get,
+	.idle = aspeed_jtag_idle,
+	.xfer = aspeed_jtag_xfer,
+	.mode_set = aspeed_jtag_mode_set
+};
+
+static int aspeed_jtag_probe(struct platform_device *pdev)
+{
+	struct aspeed_jtag *aspeed_jtag;
+	struct device *dev;
+	struct jtag *jtag;
+	int err;
+
+	dev = &pdev->dev;
+	jtag = jtag_alloc(sizeof(*aspeed_jtag), &aspeed_jtag_ops);
+	if (!jtag)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, jtag);
+	aspeed_jtag = jtag_priv(jtag);
+	aspeed_jtag->dev = &pdev->dev;
+
+	/* Initialize device*/
+	err = aspeed_jtag_init(pdev, aspeed_jtag);
+	if (err)
+		goto err_jtag_init;
+
+	/* Initialize JTAG core structure*/
+	err = jtag_register(jtag);
+	if (err)
+		goto err_jtag_register;
+
+	return 0;
+
+err_jtag_register:
+	aspeed_jtag_deinit(pdev, aspeed_jtag);
+err_jtag_init:
+	jtag_free(jtag);
+	return err;
+}
+
+static int aspeed_jtag_remove(struct platform_device *pdev)
+{
+	struct jtag *jtag;
+
+	jtag = platform_get_drvdata(pdev);
+	aspeed_jtag_deinit(pdev, jtag_priv(jtag));
+	jtag_unregister(jtag);
+	jtag_free(jtag);
+	return 0;
+}
+
+static const struct of_device_id aspeed_jtag_of_match[] = {
+	{ .compatible = "aspeed,ast2400-jtag", },
+	{ .compatible = "aspeed,ast2500-jtag", },
+	{}
+};
+
+static struct platform_driver aspeed_jtag_driver = {
+	.probe = aspeed_jtag_probe,
+	.remove = aspeed_jtag_remove,
+	.driver = {
+		.name = ASPEED_JTAG_NAME,
+		.of_match_table = aspeed_jtag_of_match,
+	},
+};
+module_platform_driver(aspeed_jtag_driver);
+
+MODULE_AUTHOR("Oleksandr Shamray <oleksandrs@mellanox.com>");
+MODULE_DESCRIPTION("ASPEED JTAG driver");
+MODULE_LICENSE("GPL v2");
-- 
1.7.1

^ permalink raw reply related

* [patch v20 3/4] Documentation: jtag: Add bindings for Aspeed SoC 24xx and 25xx families JTAG master driver
From: Oleksandr Shamray @ 2018-05-14 16:20 UTC (permalink / raw)
  To: gregkh, arnd
  Cc: linux-kernel, linux-arm-kernel, devicetree, openbmc, joel, jiri,
	tklauser, linux-serial, vadimp, system-sw-low-level, robh+dt,
	openocd-devel-owner, linux-api, davem, mchehab, Oleksandr Shamray,
	Jiri Pirko
In-Reply-To: <1526314803-3532-1-git-send-email-oleksandrs@mellanox.com>

It has been tested on Mellanox system with BMC equipped with
Aspeed 2520 SoC for programming CPLD devices.

Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com>
Signed-off-by: Jiri Pirko <jiri@mellanox.com>
Acked-by: Rob Herring <robh@kernel.org>
---
v19->v20
v18->v19
v17->v18
v16->v17
v15->v16
Comments pointed by Joel Stanley <joel.stan@gmail.com>
- change clocks = <&clk_apb> to proper clocks = <&syscon ASPEED_CLK_APB>
- add reset descriptions in bndings file

v14->v15
v13->v14
v12->v13
v11->v12
v10->v11
v9->v10
v8->v9
v7->v8
Comments pointed by pointed by Joel Stanley <joel.stan@gmail.com>
- Change compatible string to ast2400 and ast2000

V6->v7
Comments pointed by Tobias Klauser <tklauser@distanz.ch>
 - Fix spell "Doccumentation" -> "Documentation"

v5->v6
Comments pointed by Tobias Klauser <tklauser@distanz.ch>
- Small nit: s/documentation/Documentation/

v4->v5

V3->v4
Comments pointed by Rob Herring <robh@kernel.org>
- delete unnecessary "status" and "reg-shift" descriptions in
  bndings file

v2->v3
Comments pointed by Rob Herring <robh@kernel.org>
- split Aspeed jtag driver and binding to sepatrate patches
- delete unnecessary "status" and "reg-shift" descriptions in
  bndings file
---
 .../devicetree/bindings/jtag/aspeed-jtag.txt       |   22 ++++++++++++++++++++
 1 files changed, 22 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/jtag/aspeed-jtag.txt

diff --git a/Documentation/devicetree/bindings/jtag/aspeed-jtag.txt b/Documentation/devicetree/bindings/jtag/aspeed-jtag.txt
new file mode 100644
index 0000000..7c36eb6
--- /dev/null
+++ b/Documentation/devicetree/bindings/jtag/aspeed-jtag.txt
@@ -0,0 +1,22 @@
+Aspeed JTAG driver for ast2400 and ast2500 SoC
+
+Required properties:
+- compatible:		Should be one of
+      - "aspeed,ast2400-jtag"
+      - "aspeed,ast2500-jtag"
+- reg			contains the offset and length of the JTAG memory
+			region
+- clocks		root clock of bus, should reference the APB
+			clock in the second cell
+- resets		phandle to reset controller with the reset number in
+			the second cell
+- interrupts		should contain JTAG controller interrupt
+
+Example:
+jtag: jtag@1e6e4000 {
+	compatible = "aspeed,ast2500-jtag";
+	reg = <0x1e6e4000 0x1c>;
+	clocks = <&syscon ASPEED_CLK_APB>;
+	resets = <&syscon ASPEED_RESET_JTAG_MASTER>;
+	interrupts = <43>;
+};
-- 
1.7.1

^ permalink raw reply related

* [patch v20 4/4] Documentation: jtag: Add ABI documentation
From: Oleksandr Shamray @ 2018-05-14 16:20 UTC (permalink / raw)
  To: gregkh, arnd
  Cc: linux-kernel, linux-arm-kernel, devicetree, openbmc, joel, jiri,
	tklauser, linux-serial, vadimp, system-sw-low-level, robh+dt,
	openocd-devel-owner, linux-api, davem, mchehab, Oleksandr Shamray
In-Reply-To: <1526314803-3532-1-git-send-email-oleksandrs@mellanox.com>

Added document that describe the ABI for JTAG class drivrer

Signed-off-by: Oleksandr Shamray <oleksandrs@mellanox.com>
Acked-by: Arnd Bergmann <arnd@arndb.de>
---
v19->v20
Comments pointed by Randy Dunlap <rdunlap@infradead.org>
- Fix JTAG doccumentation

v18->v19
Pavel Machek <pavel@ucw.cz>
- Added JTAG doccumentation to Documentation/jtag

v17->v18
v16->v17
v15->v16
v14->v15
v13->v14
v12->v13
v11->v12
Tobias Klauser <tklauser@distanz.ch>
- rename /Documentation/ABI/testing/jatg-dev -> jtag-dev
- Typo: s/interfase/interface
v10->v11
v9->v10
Fixes added by Oleksandr:
- change jtag-cdev to jtag-dev in documentation
- update Kernel Version and Date in jtag-dev documentation;
v8->v9
v7->v8
v6->v7
Comments pointed by Pavel Machek <pavel@ucw.cz>
- Added jtag-cdev documentation to Documentation/ABI/testing folder
---
 Documentation/ABI/testing/jtag-dev |   27 +++++++++
 Documentation/jtag/overview        |   28 +++++++++
 Documentation/jtag/transactions    |  109 ++++++++++++++++++++++++++++++++++++
 3 files changed, 164 insertions(+), 0 deletions(-)
 create mode 100644 Documentation/ABI/testing/jtag-dev
 create mode 100644 Documentation/jtag/overview
 create mode 100644 Documentation/jtag/transactions

diff --git a/Documentation/ABI/testing/jtag-dev b/Documentation/ABI/testing/jtag-dev
new file mode 100644
index 0000000..4325316
--- /dev/null
+++ b/Documentation/ABI/testing/jtag-dev
@@ -0,0 +1,27 @@
+What:		/dev/jtag[0-9]+
+Date:		May 2018
+KernelVersion:	4.18
+Contact:	oleksandrs@mellanox.com
+Description:
+		The misc device files /dev/jtag* are the interface
+		between JTAG master interface and userspace.
+
+		The ioctl(2)-based ABI is defined and documented in
+		[include/uapi]<linux/jtag.h>.
+
+		The following file operations are supported:
+
+		open(2)
+		The argument flag currently support only one access
+		mode O_RDWR.
+
+		ioctl(2)
+		Initiate various actions.
+		See the inline documentation in [include/uapi]<linux/jtag.h>
+		for descriptions of all ioctls.
+
+		close(2)
+		Stops and free up the I/O contexts that was associated
+		with the file descriptor.
+
+Users:		TBD
diff --git a/Documentation/jtag/overview b/Documentation/jtag/overview
new file mode 100644
index 0000000..a86f188
--- /dev/null
+++ b/Documentation/jtag/overview
@@ -0,0 +1,28 @@
+Linux kernel JTAG support
+=========================
+
+The JTAG is an industry standard for verifying hardware
+JTAG provides access to many logic signals of a complex integrated circuit,
+including the device pins.
+
+A JTAG interface is a special interface added to a chip.
+Depending on the version of JTAG, two, four, or five pins are added.
+
+The connector pins are:
+	TDI (Test Data In)
+	TDO (Test Data Out)
+	TCK (Test Clock)
+	TMS (Test Mode Select)
+	TRST (Test Reset) optional.
+
+JTAG interface is designed to have two parts - basic core driver and
+hardware specific driver. The basic driver introduces a general interface
+which is not dependent of specific hardware. It provides communication
+between user space and hardware specific driver.
+Each JTAG device is represented as a char device from (jtag0, jtag1, ...).
+Access to a JTAG device is performed through IOCTL calls.
+
+Call flow example:
+User: open  -> /dev/jatgX
+User: ioctl -> /dev/jtagX -> JTAG core driver -> JTAG hw specific driver
+User: close -> /dev/jatgX
diff --git a/Documentation/jtag/transactions b/Documentation/jtag/transactions
new file mode 100644
index 0000000..6a857c8
--- /dev/null
+++ b/Documentation/jtag/transactions
@@ -0,0 +1,109 @@
+The JTAG API
+=============
+
+JTAG master devices can be accessed through a character misc-device.
+Each JTAG master interface can be accessed by using /dev/jtagN
+
+JTAG system calls set:
+- SIR (Scan Instruction Register, IEEE 1149.1 Instruction Register scan);
+- SDR (Scan Data Register, IEEE 1149.1 Data Register scan);
+- RUNTEST (Forces the IEEE 1149.1 bus to a run state for a specified
+number of clocks.
+
+open(), close()
+-------
+open() opens JTAG device. Only one open operation per JTAG device
+can be performed. Two or more open for one device will return error
+
+Open/Close  device:
+- open("/dev/jtag0", O_RDWR);
+- close(jtag_fd');
+
+ioctl()
+-------
+All access operations to JTAG devices performed through ioctl interface.
+The IOCTL interface supports this requests:
+	JTAG_IOCRUNTEST - Force JTAG state machine to RUN_TEST/IDLE state
+	JTAG_SIOCFREQ - Set JTAG TCK frequency
+	JTAG_GIOCFREQ - Get JTAG TCK frequency
+	JTAG_IOCXFER - send JTAG data Xfer
+	JTAG_GIOCSTATUS - get current JTAG TAP status
+	JTAG_SIOCMODE - set JTAG mode flags.
+
+JTAG_SIOCFREQ, JTAG_GIOCFREQ
+------
+Set/Get JTAG clock speed:
+
+	unsigned int jtag_fd;
+	ioctl(jtag_fd, JTAG_SIOCFREQ, &frq);
+	ioctl(jtag_fd, JTAG_GIOCFREQ, &frq);
+
+JTAG_IOCRUNTEST
+------
+Force JTAG state machine to RUN_TEST/IDLE state
+
+struct jtag_run_test_idle {
+	__u8	reset;
+	__u8	endstate;
+	__u8	tck;
+};
+
+reset: 0 - run IDLE/PAUSE from current state
+	   1 - go through TEST_LOGIC/RESET state before  IDLE/PAUSE
+endstate: completion flag
+tck: clock counter
+
+Example:
+	struct jtag_run_test_idle runtest;
+
+	runtest.endstate = JTAG_STATE_IDLE;
+	runtest.reset = 0;
+	runtest.tck = data_p->tck;
+	usleep(25 * 1000);
+	ioctl(jtag_fd, JTAG_IOCRUNTEST, &runtest);
+
+JTAG_IOCXFER
+------
+Send SDR/SIR transaction
+
+struct jtag_xfer {
+	__u8	type;
+	__u8	direction;
+	__u8	endstate;
+	__u8	padding;
+	__u32	length;
+	__u64	tdio;
+};
+
+type: transfer type - JTAG_SIR_XFER/JTAG_SDR_XFER
+direction: xfer direction - JTAG_SIR_XFER/JTAG_SDR_XFER,
+length: xfer data len in bits
+tdio : xfer data array
+endstate: xfer end state after transaction finish
+	   can be: JTAG_STATE_IDLE/JTAG_STATE_PAUSEIR/JTAG_STATE_PAUSEDR
+
+Example:
+	struct jtag_xfer xfer;
+	static char buf[64];
+	static unsigned int buf_len = 0;
+	[...]
+	xfer.type = JTAG_SDR_XFER;
+	xfer.tdio = (__u64)buf;
+	xfer.length = buf_len;
+	xfer.endstate = JTAG_STATE_IDLE;
+
+	if (is_read)
+		xfer.direction = JTAG_READ_XFER;
+	else
+		xfer.direction = JTAG_WRITE_XFER;
+
+	ioctl(jtag_fd, JTAG_IOCXFER, &xfer);
+
+JTAG_SIOCMODE
+------
+If hw driver can support different running modes you can change it.
+
+Example:
+	unsigned int mode;
+	mode = JTAG_XFER_HW_MODE;
+	ioctl(jtag_fd, JTAG_SIOCMODE, &mode);
-- 
1.7.1

^ permalink raw reply related

* Re: [PATCH 2/4] PCI: dwc: Add support for EP mode
From: Joao Pinto @ 2018-05-14 16:23 UTC (permalink / raw)
  To: Gustavo Pimentel, bhelgaas, lorenzo.pieralisi, Joao.Pinto,
	jingoohan1, kishon, robh+dt, mark.rutland
  Cc: linux-pci, linux-kernel, devicetree
In-Reply-To: <dbfaccc9902e43b659584e4638a26549a363292a.1526314398.git.gustavo.pimentel@synopsys.com>

Às 5:17 PM de 5/14/2018, Gustavo Pimentel escreveu:
> The PCIe controller dual mode is capable of operating in RC mode as well
> as EP mode by configuration option. Till now only RC mode was supported,
> with this patch is add EP support to the DesignWare driver.
> 
> Add new property on pci_epc structure which allow to configure
> pci_epf_test driver accordingly to the controller specific requirements.
> 
> Signed-off-by: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
> Acked-by: Kishon Vijay Abraham I <kishon@ti.com>
> ---
>  drivers/pci/dwc/Kconfig                       |  41 +++++--
>  drivers/pci/dwc/pcie-designware-ep.c          |   3 +
>  drivers/pci/dwc/pcie-designware-plat.c        | 149 ++++++++++++++++++++++++--
>  drivers/pci/endpoint/functions/pci-epf-test.c |   7 ++
>  include/linux/pci-epc.h                       |   8 ++
>  5 files changed, 187 insertions(+), 21 deletions(-)
> 
> diff --git a/drivers/pci/dwc/Kconfig b/drivers/pci/dwc/Kconfig
> index 2f3f5c5..62f7cdf 100644
> --- a/drivers/pci/dwc/Kconfig
> +++ b/drivers/pci/dwc/Kconfig
> @@ -7,8 +7,7 @@ config PCIE_DW
>  
>  config PCIE_DW_HOST
>          bool
> -	depends on PCI
> -	depends on PCI_MSI_IRQ_DOMAIN
> +	depends on PCI && PCI_MSI_IRQ_DOMAIN
>          select PCIE_DW
>  
>  config PCIE_DW_EP
> @@ -51,17 +50,37 @@ config PCI_DRA7XX_EP
>  	  This uses the DesignWare core.
>  
>  config PCIE_DW_PLAT
> -	bool "Platform bus based DesignWare PCIe Controller"
> -	depends on PCI
> -	depends on PCI_MSI_IRQ_DOMAIN
> -	select PCIE_DW_HOST
> -	---help---
> -	 This selects the DesignWare PCIe controller support. Select this if
> -	 you have a PCIe controller on Platform bus.
> +	bool
>  
> -	 If you have a controller with this interface, say Y or M here.
> +config PCIE_DW_PLAT_HOST
> +	bool "Platform bus based DesignWare PCIe Controller - Host mode"
> +	depends on PCI && PCI_MSI_IRQ_DOMAIN
> +	select PCIE_DW_HOST
> +	select PCIE_DW_PLAT
> +	default y
> +	help
> +	  Enables support for the PCIe controller in the Designware IP to
> +	  work in host mode. There are two instances of PCIe controller in
> +	  Designware IP.
> +	  This controller can work either as EP or RC. In order to enable
> +	  host-specific features PCIE_DW_PLAT_HOST must be selected and in
> +	  order to enable device-specific features PCI_DW_PLAT_EP must be
> +	  selected.
>  
> -	 If unsure, say N.
> +config PCIE_DW_PLAT_EP
> +	bool "Platform bus based DesignWare PCIe Controller - Endpoint mode"
> +	depends on PCI && PCI_MSI_IRQ_DOMAIN
> +	depends on PCI_ENDPOINT
> +	select PCIE_DW_EP
> +	select PCIE_DW_PLAT
> +	help
> +	  Enables support for the PCIe controller in the Designware IP to
> +	  work in endpoint mode. There are two instances of PCIe controller
> +	  in Designware IP.
> +	  This controller can work either as EP or RC. In order to enable
> +	  host-specific features PCIE_DW_PLAT_HOST must be selected and in
> +	  order to enable device-specific features PCI_DW_PLAT_EP must be
> +	  selected.
>  
>  config PCI_EXYNOS
>  	bool "Samsung Exynos PCIe controller"
> diff --git a/drivers/pci/dwc/pcie-designware-ep.c b/drivers/pci/dwc/pcie-designware-ep.c
> index f07678b..2c05a08 100644
> --- a/drivers/pci/dwc/pcie-designware-ep.c
> +++ b/drivers/pci/dwc/pcie-designware-ep.c
> @@ -411,6 +411,9 @@ int dw_pcie_ep_init(struct dw_pcie_ep *ep)
>  		return -ENOMEM;
>  	}
>  
> +	epc->features = EPC_FEATURE_NO_LINKUP_NOTIFIER;
> +	EPC_FEATURE_SET_BAR(epc->features, BAR0);
> +
>  	ep->epc = epc;
>  	epc_set_drvdata(epc, ep);
>  	dw_pcie_setup(pci);
> diff --git a/drivers/pci/dwc/pcie-designware-plat.c b/drivers/pci/dwc/pcie-designware-plat.c
> index 5416aa8..efc315c 100644
> --- a/drivers/pci/dwc/pcie-designware-plat.c
> +++ b/drivers/pci/dwc/pcie-designware-plat.c
> @@ -12,19 +12,29 @@
>  #include <linux/interrupt.h>
>  #include <linux/kernel.h>
>  #include <linux/init.h>
> +#include <linux/of_device.h>
>  #include <linux/of_gpio.h>
>  #include <linux/pci.h>
>  #include <linux/platform_device.h>
>  #include <linux/resource.h>
>  #include <linux/signal.h>
>  #include <linux/types.h>
> +#include <linux/regmap.h>
>  
>  #include "pcie-designware.h"
>  
>  struct dw_plat_pcie {
> -	struct dw_pcie		*pci;
> +	struct dw_pcie			*pci;
> +	struct regmap			*regmap;
> +	enum dw_pcie_device_mode	mode;
>  };
>  
> +struct dw_plat_pcie_of_data {
> +	enum dw_pcie_device_mode	mode;
> +};
> +
> +static const struct of_device_id dw_plat_pcie_of_match[];
> +
>  static int dw_plat_pcie_host_init(struct pcie_port *pp)
>  {
>  	struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
> @@ -42,9 +52,53 @@ static const struct dw_pcie_host_ops dw_plat_pcie_host_ops = {
>  	.host_init = dw_plat_pcie_host_init,
>  };
>  
> -static int dw_plat_add_pcie_port(struct pcie_port *pp,
> +static int dw_plat_pcie_establish_link(struct dw_pcie *pci)
> +{
> +	return 0;
> +}
> +
> +static const struct dw_pcie_ops dw_pcie_ops = {
> +	.start_link = dw_plat_pcie_establish_link,
> +};
> +
> +static void dw_plat_pcie_ep_init(struct dw_pcie_ep *ep)
> +{
> +	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
> +	enum pci_barno bar;
> +
> +	for (bar = BAR_0; bar <= BAR_5; bar++)
> +		dw_pcie_ep_reset_bar(pci, bar);
> +}
> +
> +static int dw_plat_pcie_ep_raise_irq(struct dw_pcie_ep *ep, u8 func_no,
> +				     enum pci_epc_irq_type type,
> +				     u8 interrupt_num)
> +{
> +	struct dw_pcie *pci = to_dw_pcie_from_ep(ep);
> +
> +	switch (type) {
> +	case PCI_EPC_IRQ_LEGACY:
> +		dev_err(pci->dev, "EP cannot trigger legacy IRQs\n");
> +		return -EINVAL;
> +	case PCI_EPC_IRQ_MSI:
> +		return dw_pcie_ep_raise_msi_irq(ep, func_no, interrupt_num);
> +	default:
> +		dev_err(pci->dev, "UNKNOWN IRQ type\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static struct dw_pcie_ep_ops pcie_ep_ops = {
> +	.ep_init = dw_plat_pcie_ep_init,
> +	.raise_irq = dw_plat_pcie_ep_raise_irq,
> +};
> +
> +static int dw_plat_add_pcie_port(struct dw_plat_pcie *dw_plat_pcie,
>  				 struct platform_device *pdev)
>  {
> +	struct dw_pcie *pci = dw_plat_pcie->pci;
> +	struct pcie_port *pp = &pci->pp;
>  	struct device *dev = &pdev->dev;
>  	int ret;
>  
> @@ -63,15 +117,44 @@ static int dw_plat_add_pcie_port(struct pcie_port *pp,
>  
>  	ret = dw_pcie_host_init(pp);
>  	if (ret) {
> -		dev_err(dev, "failed to initialize host\n");
> +		dev_err(dev, "Failed to initialize host\n");
>  		return ret;
>  	}
>  
>  	return 0;
>  }
>  
> -static const struct dw_pcie_ops dw_pcie_ops = {
> -};
> +static int dw_plat_add_pcie_ep(struct dw_plat_pcie *dw_plat_pcie,
> +			       struct platform_device *pdev)
> +{
> +	int ret;
> +	struct dw_pcie_ep *ep;
> +	struct resource *res;
> +	struct device *dev = &pdev->dev;
> +	struct dw_pcie *pci = dw_plat_pcie->pci;
> +
> +	ep = &pci->ep;
> +	ep->ops = &pcie_ep_ops;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi2");
> +	pci->dbi_base2 = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(pci->dbi_base2))
> +		return PTR_ERR(pci->dbi_base2);
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "addr_space");
> +	if (!res)
> +		return -EINVAL;
> +
> +	ep->phys_base = res->start;
> +	ep->addr_size = resource_size(res);
> +
> +	ret = dw_pcie_ep_init(ep);
> +	if (ret) {
> +		dev_err(dev, "Failed to initialize endpoint\n");
> +		return ret;
> +	}
> +	return 0;
> +}
>  
>  static int dw_plat_pcie_probe(struct platform_device *pdev)
>  {
> @@ -80,6 +163,16 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
>  	struct dw_pcie *pci;
>  	struct resource *res;  /* Resource from DT */
>  	int ret;
> +	const struct of_device_id *match;
> +	const struct dw_plat_pcie_of_data *data;
> +	enum dw_pcie_device_mode mode;
> +
> +	match = of_match_device(dw_plat_pcie_of_match, dev);
> +	if (!match)
> +		return -EINVAL;
> +
> +	data = (struct dw_plat_pcie_of_data *)match->data;
> +	mode = (enum dw_pcie_device_mode)data->mode;
>  
>  	dw_plat_pcie = devm_kzalloc(dev, sizeof(*dw_plat_pcie), GFP_KERNEL);
>  	if (!dw_plat_pcie)
> @@ -93,23 +186,59 @@ static int dw_plat_pcie_probe(struct platform_device *pdev)
>  	pci->ops = &dw_pcie_ops;
>  
>  	dw_plat_pcie->pci = pci;
> +	dw_plat_pcie->mode = mode;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dbi");
> +	if (!res)
> +		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  
> -	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	pci->dbi_base = devm_ioremap_resource(dev, res);
>  	if (IS_ERR(pci->dbi_base))
>  		return PTR_ERR(pci->dbi_base);
>  
>  	platform_set_drvdata(pdev, dw_plat_pcie);
>  
> -	ret = dw_plat_add_pcie_port(&pci->pp, pdev);
> -	if (ret < 0)
> -		return ret;
> +	switch (dw_plat_pcie->mode) {
> +	case DW_PCIE_RC_TYPE:
> +		if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_HOST))
> +			return -ENODEV;
> +
> +		ret = dw_plat_add_pcie_port(dw_plat_pcie, pdev);
> +		if (ret < 0)
> +			return ret;
> +		break;
> +	case DW_PCIE_EP_TYPE:
> +		if (!IS_ENABLED(CONFIG_PCIE_DW_PLAT_EP))
> +			return -ENODEV;
> +
> +		ret = dw_plat_add_pcie_ep(dw_plat_pcie, pdev);
> +		if (ret < 0)
> +			return ret;
> +		break;
> +	default:
> +		dev_err(dev, "INVALID device type %d\n", dw_plat_pcie->mode);
> +	}
>  
>  	return 0;
>  }
>  
> +static const struct dw_plat_pcie_of_data dw_plat_pcie_rc_of_data = {
> +	.mode = DW_PCIE_RC_TYPE,
> +};
> +
> +static const struct dw_plat_pcie_of_data dw_plat_pcie_ep_of_data = {
> +	.mode = DW_PCIE_EP_TYPE,
> +};
> +
>  static const struct of_device_id dw_plat_pcie_of_match[] = {
> -	{ .compatible = "snps,dw-pcie", },
> +	{
> +		.compatible = "snps,dw-pcie",
> +		.data = &dw_plat_pcie_rc_of_data,
> +	},
> +	{
> +		.compatible = "snps,dw-pcie-ep",
> +		.data = &dw_plat_pcie_ep_of_data,
> +	},
>  	{},
>  };
>  
> diff --git a/drivers/pci/endpoint/functions/pci-epf-test.c b/drivers/pci/endpoint/functions/pci-epf-test.c
> index 7cef851..bee401d 100644
> --- a/drivers/pci/endpoint/functions/pci-epf-test.c
> +++ b/drivers/pci/endpoint/functions/pci-epf-test.c
> @@ -435,6 +435,13 @@ static int pci_epf_test_bind(struct pci_epf *epf)
>  	if (WARN_ON_ONCE(!epc))
>  		return -EINVAL;
>  
> +	if (epc->features & EPC_FEATURE_NO_LINKUP_NOTIFIER)
> +		epf_test->linkup_notifier = false;
> +	else
> +		epf_test->linkup_notifier = true;
> +
> +	epf_test->test_reg_bar = EPC_FEATURE_GET_BAR(epc->features);
> +
>  	ret = pci_epc_write_header(epc, epf->func_no, header);
>  	if (ret) {
>  		dev_err(dev, "configuration header write failed\n");
> diff --git a/include/linux/pci-epc.h b/include/linux/pci-epc.h
> index af657ca..243eaa5 100644
> --- a/include/linux/pci-epc.h
> +++ b/include/linux/pci-epc.h
> @@ -90,8 +90,16 @@ struct pci_epc {
>  	struct config_group		*group;
>  	/* spinlock to protect against concurrent access of EP controller */
>  	spinlock_t			lock;
> +	unsigned int			features;
>  };
>  
> +#define EPC_FEATURE_NO_LINKUP_NOTIFIER		BIT(0)
> +#define EPC_FEATURE_BAR_MASK			(BIT(1) | BIT(2) | BIT(3))
> +#define EPC_FEATURE_SET_BAR(features, bar)	\
> +		(features |= (EPC_FEATURE_BAR_MASK & (bar << 1)))
> +#define EPC_FEATURE_GET_BAR(features)		\
> +		((features & EPC_FEATURE_BAR_MASK) >> 1)
> +
>  #define to_pci_epc(device) container_of((device), struct pci_epc, dev)
>  
>  #define pci_epc_create(dev, ops)    \
> 


Acked-by: Joao Pinto <jpinto@synopsys.com>

^ permalink raw reply

* Re: [PATCH 03/21] clk: sunxi-ng: Enable DE2_CCU for Allwinner 64-bit SoCs
From: Chen-Yu Tsai @ 2018-05-14 16:43 UTC (permalink / raw)
  To: Jagan Teki
  Cc: Maxime Ripard, Icenowy Zheng, Jernej Skrabec, Rob Herring,
	Mark Rutland, Catalin Marinas, Will Deacon, David Airlie,
	dri-devel, Michael Turquette, Stephen Boyd, linux-clk,
	Michael Trimarchi, linux-arm-kernel, devicetree, linux-kernel,
	linux-sunxi
In-Reply-To: <CAMty3ZAOVg6EZoMi=LRuKEcrnDM_tzYfK1WnBN-_d-pmjNS6Nw-JsoAwUIsXosN+BqQ9rBEUg@public.gmane.org>

On Mon, May 14, 2018 at 1:20 AM, Jagan Teki <jagan-dyjBcgdgk7Pe9wHmmfpqLFaTQe2KTcn/@public.gmane.org> wrote:
> On Tue, May 1, 2018 at 9:53 PM, Chen-Yu Tsai <wens-jdAy2FN1RRM@public.gmane.org> wrote:
>> On Mon, Apr 30, 2018 at 7:40 PM, Jagan Teki <jagan-dyjBcgdgk7Pe9wHmmfpqLFaTQe2KTcn/@public.gmane.org> wrote:
>>> Allwinner 64-bit SoC like H5/A64 has DE2 CCU so enable them
>>> as default.
>>>
>>> Signed-off-by: Jagan Teki <jagan-dyjBcgdgk7Pe9wHmmfpqLFaTQe2KTcn/@public.gmane.org>
>>> ---
>>>  drivers/clk/sunxi-ng/Kconfig | 2 ++
>>>  1 file changed, 2 insertions(+)
>>>
>>> diff --git a/drivers/clk/sunxi-ng/Kconfig b/drivers/clk/sunxi-ng/Kconfig
>>> index 79dfd296c3d1..1fffd3bf6ff3 100644
>>> --- a/drivers/clk/sunxi-ng/Kconfig
>>> +++ b/drivers/clk/sunxi-ng/Kconfig
>>> @@ -58,6 +58,8 @@ config SUN8I_V3S_CCU
>>>
>>>  config SUN8I_DE2_CCU
>>>         bool "Support for the Allwinner SoCs DE2 CCU"
>>> +       default ARM64 && ARCH_SUNXI
>>> +       depends on (DRM_SUN4I && (ARM64 && ARCH_SUNXI)) || COMPILE_TEST
>>
>> There is no reason to depend on DRM_SUN4I. There is no compile dependency.
>
> Since this CCU is for DE2 I've make DRM_SUN4I for that matter, any problem?

Users may very well be just using simplefb, but with all the clock drivers
enabled. That is a valid, if very limited, use case. So again, you should
not limit this driver based on DRM_SUN4I. There is no compile dependency.

>
>>
>> Also, this is needed on SUN8I as well, pretty much anything with DE 2.0.
>> So you shouldn't limit it to ARM64. That pretty much breaks things for
>> people with A83T's or H3's. In fact you should enable it by default for
>> these as well.
>
> True, I've skipped SUN8I since this series for A64.

And yet you are breaking existing users, which is a big no-no.

ChenYu

^ permalink raw reply

* Re: [PATCH 3/3] arm64: dts: renesas: draak: Describe HDMI input
From: Laurent Pinchart @ 2018-05-14 16:52 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: Jacopo Mondi, horms, geert, magnus.damm, robh+dt,
	linux-renesas-soc, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20180514094900.GF30519@bigcity.dyn.berto.se>

Hi Niklas,

On Monday, 14 May 2018 12:49:00 EEST Niklas Söderlund wrote:
> On 2018-05-14 05:49:41 +0300, Laurent Pinchart wrote:
> 
> [snip]
> 
> >>> +&vin4 {
> >>> +	pinctrl-0 = <&vin4_pins>;
> >>> +	pinctrl-names = "default";
> >>> +
> >>> +	status = "okay";
> >>> +
> >>> +	ports {
> >>> +		#address-cells = <1>;
> >>> +		#size-cells = <0>;
> >>> +
> >>> +		port@0 {
> >>> +			reg = <0>;
> >>> +			vin4_in: endpoint {
> >>> +				hsync-active = <0>;
> >>> +				vsync-active = <0>;
> >> 
> >> Comparing this to the Gen2 bindings some properties are missing,
> >> 
> >> bus-width = <24>;
> >> pclk-sample = <1>;
> >> data-active = <1>;
> >> 
> >> This is not a big deal as the VIN driver don't use these properties so
> >> no functional change should come of this but still a difference.
> > 
> > I think the VIN DT bindings should be updated to explicitly list the
> > endpoint properties that are mandatory, optional, or not allowed.
> 
> I think it's documented as it reference video-interfaces.txt which lists
> all these properties as optional. And in deed they are all optional.

I don't think that's good enough. They're all listed as optional in video-
interfaces.txt as the generic documentation can't know whether a particular 
device will require a particular property or not. It's the responsibility of 
device DT bindings to refine the bindings description. The VIN DT bindings 
should explicitly list the properties that apply to the VIN and tell whether 
they're optional or mandatory for the VIN. For optional properties, the 
default behaviour when the property is not specified should be documented too.

For instance, does VIN support selecting which pixel clock edge to sample data 
on ? If so the pclk-sample property should listed as either mandatory or 
optional with a documented default, even if not used by the driver today.

> If the VIN driver makes use of all the optional ones is another matter. How
> do we know that the remote subdevice is not looking at its remote
> endpoint for bus parameters not considered by the rcar-vin driver?

No driver should parse properties of remote nodes, as those properties are to 
be interpreted in the context of the remote node's DT bindings, which the 
driver doesn't know about. Parsing OF graph properties (ports and endpoints) 
is an exception, as by connecting a remote node to the local node with OF 
graph properties you imply that the remote node uses OF graph DT bindings, so 
those properties (and only those properties) can be parsed.

> The thing is that the rcar-vin driver only looks at the remote endpoint
> for these properties and ignores the on its local endpoint. Maybe some
> v4l2 framework change is needed here to make sure the bus properties are
> the same on both endpoints of a link. But I fear such a change would
> break a lot of stuff.

Properties are specified on both endpoints to account for components such as 
inverter gates between the two devices. They can thus be different on the two 
sides, that's perfectly valid. The VIN driver should parse its local 
properties, not the remote properties.

-- 
Regards,

Laurent Pinchart

^ permalink raw reply

* Re: [PATCH v3 6/6] tty/serial: atmel: changed the driver to work under at91-usart mfd
From: Andy Shevchenko @ 2018-05-14 16:56 UTC (permalink / raw)
  To: Richard Genoud
  Cc: Radu Pirea, devicetree, open list:SERIAL DRIVERS,
	Linux Kernel Mailing List, linux-arm Mailing List, linux-spi,
	Mark Rutland, Rob Herring, Lee Jones, Greg Kroah-Hartman,
	Jiri Slaby, alexandre.belloni, Nicolas Ferre, Mark Brown
In-Reply-To: <4a75568e-a52e-3872-f465-d707372a5c12@sorico.fr>

On Mon, May 14, 2018 at 1:57 PM, Richard Genoud
<richard.genoud@gmail.com> wrote:
> On 11/05/2018 12:38, Radu Pirea wrote:
>> This patch modifies the place where resources and device tree properties
>> are searched.

> I think it may be simpler with something like:

> +       int size = mfd_pdev->resource[0].end - mfd_pdev->resource[0].start + 1;

Isn't resource_size() macro for this very purpose?

>> +     int size = to_platform_device(pdev->dev.parent)->resource[0].end -
>> +             to_platform_device(pdev->dev.parent)->resource[0].start + 1;
>>
> ditto

Ditto.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply

* Re: [PATCH V1 2/5] backlight: qcom-wled: Add support for WLED4 peripheral
From: Pavel Machek @ 2018-05-14 16:57 UTC (permalink / raw)
  To: Kiran Gunda
  Cc: bjorn.andersson, Lee Jones, Daniel Thompson, Jingoo Han,
	Jacek Anaszewski, Rob Herring, Mark Rutland,
	Bartlomiej Zolnierkiewicz, linux-leds, devicetree, linux-kernel,
	dri-devel, linux-fbdev, linux-arm-msm
In-Reply-To: <1525341432-15818-3-git-send-email-kgunda@codeaurora.org>

Hi!

> WLED4 peripheral is present on some PMICs like pmi8998
> and pm660l. It has a different register map and also
> configurations are different. Add support for it.
> 
> Signed-off-by: Kiran Gunda <kgunda@codeaurora.org>
> ---
>  .../bindings/leds/backlight/qcom-wled.txt          | 172 ++++-
>  drivers/video/backlight/qcom-wled.c                | 749 +++++++++++++++------
>  2 files changed, 696 insertions(+), 225 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/leds/backlight/qcom-wled.txt b/Documentation/devicetree/bindings/leds/backlight/qcom-wled.txt
> index fb39e32..0ceffa1 100644
> --- a/Documentation/devicetree/bindings/leds/backlight/qcom-wled.txt
> +++ b/Documentation/devicetree/bindings/leds/backlight/qcom-wled.txt
> @@ -1,30 +1,129 @@
>  Binding for Qualcomm Technologies, Inc. WLED driver
>  
> -Required properties:
> -- compatible: should be "qcom,pm8941-wled"
> -- reg: slave address
> -
> -Optional properties:
> -- default-brightness: brightness value on boot, value from: 0-4095
> -	default: 2048
> +- compatible
> +	Usage:        required
> +	Value type:   <string>
> +	Definition:   should be "qcom,pm8941-wled" or "qcom,pmi8998-wled".
> +		      or "qcom,pm660l-wled".
> +
> +- reg
> +	Usage:        required
> +	Value type:   <prop encoded array>
> +	Definition:   Base address of the WLED modules.

I'm not sure if this change of format is good idea here...

									Pavel

^ permalink raw reply

* Re: [PATCH v4 2/2] leds: Add Spreadtrum SC27xx breathing light controller driver
From: Pavel Machek @ 2018-05-14 16:58 UTC (permalink / raw)
  To: Baolin Wang
  Cc: jacek.anaszewski, robh+dt, mark.rutland, xiaotong.lu, broonie,
	linux-leds, devicetree, linux-kernel
In-Reply-To: <f20d9b010a256b695dbaefedacdf3d29fd4b9674.1526279287.git.baolin.wang@linaro.org>

On Mon 2018-05-14 14:34:51, Baolin Wang wrote:
> This patch adds Spreadtrum SC27xx PMIC series breathing light controller
> driver, which can support 3 LEDs. Each LED can work at normal PWM mode
> and breathing mode.
> 
> Signed-off-by: Xiaotong Lu <xiaotong.lu@spreadtrum.com>
> Signed-off-by: Baolin Wang <baolin.wang@linaro.org>

ACK.

									Pavel

^ permalink raw reply

* Re: [PATCH 3/3] arm64: dts: renesas: draak: Describe HDMI input
From: Laurent Pinchart @ 2018-05-14 17:03 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: jacopo mondi, Jacopo Mondi, horms, geert, magnus.damm, robh+dt,
	linux-renesas-soc, devicetree, linux-arm-kernel, linux-kernel
In-Reply-To: <20180514102326.GG30519@bigcity.dyn.berto.se>

Hi Niklas,

On Monday, 14 May 2018 13:23:26 EEST Niklas Söderlund wrote:
> On 2018-05-14 09:39:34 +0200, Jacopo Mondi wrote:
> > On Sun, May 13, 2018 at 02:57:55PM +0200, Niklas Söderlund wrote:
> >> On 2018-05-11 12:00:02 +0200, Jacopo Mondi wrote:
> >>> Describe HDMI input connected to VIN4 interface for R-Car D3 Draak
> >>> development board.
> >>> 
> >>> Signed-off-by: Jacopo Mondi <jacopo+renesas@jmondi.org>
> >>> ---
> >>> 
> >>>  arch/arm64/boot/dts/renesas/r8a77995-draak.dts | 68 +++++++++++++++++++
> >>>  1 file changed, 68 insertions(+)
> >>> 
> >>> diff --git a/arch/arm64/boot/dts/renesas/r8a77995-draak.dts
> >>> b/arch/arm64/boot/dts/renesas/r8a77995-draak.dts index
> >>> d03f194..e0ce462 100644
> >>> --- a/arch/arm64/boot/dts/renesas/r8a77995-draak.dts
> >>> +++ b/arch/arm64/boot/dts/renesas/r8a77995-draak.dts

[snip]

> >>> +&vin4 {
> >>> +	pinctrl-0 = <&vin4_pins>;
> >>> +	pinctrl-names = "default";
> >>> +
> >>> +	status = "okay";
> >>> +
> >>> +	ports {
> >>> +		#address-cells = <1>;
> >>> +		#size-cells = <0>;
> >>> +
> >>> +		port@0 {
> >>> +			reg = <0>;
> >>> +
> >>> +			vin4_in: endpoint {
> >>> +				hsync-active = <0>;
> >>> +				vsync-active = <0>;
> >> 
> >> Comparing this to the Gen2 bindings some properties are missing,
> >> 
> >> bus-width = <24>;
> >> pclk-sample = <1>;
> >> data-active = <1>;
> > 
> > The VIN driver does not parse them, so there is no value in having
> > them there, if not confusing people as it happened to me reading the
> > Gen2 DT.
> 
> I have no objection removing them. Trying to understand why the
> description differed from Gen2.
> 
> >> This is not a big deal as the VIN driver don't use these properties so
> >> no functional change should come of this but still a difference.
> > 
> > Exactly.
> > 
> > On a side note. I have not seen a way to configure the pixel clock
> > sampling level in the interface datasheet. The register used to
> > configure synchronism signals polarities is VnDMR2, and there I read
> > we can configure HSYNC/VSYNC and CLOCKENB (which is data enable, not
> > pixel clock) polarities. Is it configured through some other
> > register?
> 
> I have not seen such a register no.
> 
> >> Over all I'm happy with this change but before I add my tag I would like
> >> to understand why it differs from the Gen2 configuration for the adv7612
> >> properties.
> >> 
> >> Also on a side not it is possible with hardware switches on the board
> >> switch the VIN4 source to a completely different pipeline CVBS connector
> >> -> adv7180 -> VIN4. But I think it's best we keep the HDMI as default as
> >> this seems to be how the boards are shipped. But maybe mentioning this
> >> in the commit message would not hurt if you end-up resending the patch.
> > 
> > Oh I see. SW-49 to SW-52 enables the HDMI input, SW53-SW54 CVBS one.
> > And actually, reading the 'initial setting of slide switches' in the
> > Draak board manual, it turns out that the board default configuration
> > is with CVBS input selected... What should we do here? reflect
> > defaults in the DT, or prioritize HDMI?
> 
> I feel this is a question for Laurent. My feeling for how we handled
> this in other cases is to go with the board default settings. I'm
> however sure there are exceptions to the rule. So maybe we should go
> with the most useful (what ever that is) configuration?

I think I'd go with CVBS as I don't think HDMI would be considered as the most 
useful configuration here. The Draak board is unlikely to be used by us as a 
reference platform to test HDMI capture, is it ?

This being said, you can instantiate the adv7612 and HDMI connector in DT, 
without connecting them to the VIN. That would make it easy to quickly change 
the configuration.

> >>> +
> >>> +				remote-endpoint = <&adv7612_out>;
> >>> +			};
> >>> +		};
> >>> +	};
> >>> +};

-- 
Regards,

Laurent Pinchart

^ permalink raw reply

* Re: [PATCH v2 1/2] dt-bindings/display/bridge: sii902x: add optional power supplies
From: Laurent Pinchart @ 2018-05-14 17:06 UTC (permalink / raw)
  To: Philippe CORNU
  Cc: Mark Rutland, Alexandre TORGUE, devicetree@vger.kernel.org,
	David Airlie, linux-kernel@vger.kernel.org,
	dri-devel@lists.freedesktop.org, Yannick FERTRE
In-Reply-To: <7fe8e766-56f5-38a3-b4dd-b44d4a5bdcf8@st.com>

Hi Philippe,

On Monday, 14 May 2018 12:22:16 EEST Philippe CORNU wrote:
> On 04/26/2018 12:05 AM, Laurent Pinchart wrote:
> > On Wednesday, 25 April 2018 20:11:23 EEST Rob Herring wrote:
> >> On Wed, Apr 25, 2018 at 04:17:25PM +0300, Laurent Pinchart wrote:
> >>> On Wednesday, 25 April 2018 15:20:04 EEST Philippe CORNU wrote:
> >>>> On 04/25/2018 11:01 AM, Laurent Pinchart wrote:
> >>>>> On Wednesday, 25 April 2018 10:53:13 EEST Philippe Cornu wrote:
> >>>>>> Add optional power supplies using the description found in
> >>>>>> "SiI9022A/SiI9024A HDMI Transmitter Data Sheet (August 2016)".
> >>>>>>
> >>>>>> There is a single 1v2 supply voltage named vcc12 from which cvcc12
> >>>>>> (digital core) and avcc12 (TMDS analog) are derived because
> >>>>>> according to this data sheet:
> >>>>>> "cvcc12 and avcc12 can be derived from the same power source"
> >>>>>
> >>>>> Shouldn't the power supplies be mandatory, as explained by Mark in
> >>>>> https://lists.freedesktop.org/archives/dri-devel/2018-April/
> >>>>> 172400.html ?
> >>>>
> >>>> Laurent,
> >>>> Many thanks Laurent for your comment, I understood the merge of the
> >>>> two 1v2 power supplies but missed the "mandatory" part... maybe because
> >>>> this patch (with optional power supplies) already got the reviewed-by
> >>>> from Rob, I thought the discussion thread you pointed out was
> >>>> applicable "only" to totally new driver documentation.
> >>>>
> >>>> So, on my side, as a "new user" of sii902x IC, no problem to put these
> >>>> power supplies as mandatory instead of optional properties but I would
> >>>> like to be sure this is applicable to both old and new bindings doc :
> >>>> )
> >>>
> >>> We obviously need to retain backward compatibility, so on the driver
> >>> side you need to treat those power supplies as optional. From a DT
> >>> bindings point of view, however, I think they should be mandatory for
> >>> new DT.
> >>
> >> We don't really have a way to describe these 3 conditions (required for
> >> all, optional for all, and required for new). So generally we make
> >> additions optional. The exception sometimes is if we update all the dts
> >> files.
> > 
> > Can't we just make it mandatory in the bindings, as long as we treat it
> > as optional in drivers ?
> 
> How to progress on this patch? Do you have any suggestions?

By seeing what Rob thinks about my proposal above ? :-)

> >>>> Rob,
> >>>> could you please confirm these power supply properties should be
> >>>> "mandatory"? if yes, should we then modify other optional properties
> >>>> like the reset-gpios too in the future?
> >>>
> >>> The GPIOs properties are different in my opinion, as there's no
> >>> requirement to connect for instance the reset pin to a GPIO controllable
> >>> by the SoC. The pin could be hardwired to VCC, or connected to a system
> >>> reset that is automatically managed without SoC intervention. The power
> >>> supplies, however, are mandatory, in the sense that the chip will not
> >>> work if you leave the power supplies unconnected.
> >>
> >> DT only needs to describe what matters to s/w. If a regulator is
> >> fixed and you don't need to know its voltage (or other read-only
> >> parameters), then there's not much point in putting it in DT.
> >>
> >> I'd probably base this more at a platform level and you either use
> >> regulator binding or you don't. It's perfectly valid that you want to do
> >> things like regulator setup, pin ctrl and muxing setup, etc. all in
> >> firmware and the OS doesn't touch any of that.
> >>
> >> That's all a big can of worms which we shouldn't solve on this 2 line
> >> change. I think this change is fine as-is, so:
> >>
> >> Reviewed-by: Rob Herring <robh@kernel.org>

-- 
Regards,

Laurent Pinchart



_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox