On Thu, Dec 14, 2023 at 05:04:14PM +0200, Elad Nachman wrote: > From: Elad Nachman <enachman@xxxxxxxxxxx> > > Add support for Marvell ac5/x variant of the ARM > sbsa global watchdog. This watchdog deviates from > the standard driver by the following items: > > 1. Registers reside in secure register section. > hence access is only possible via SMC calls to ATF. > > 2. There are couple more registers which reside in > other register areas, which needs to be configured > in order for the watchdog to properly generate > reset through the SOC. > > The new Marvell compatibility string differentiates between > the original sbsa mode of operation and the Marvell mode of > operation. > > Signed-off-by: Elad Nachman <enachman@xxxxxxxxxxx> > --- > drivers/watchdog/sbsa_gwdt.c | 247 ++++++++++++++++++++++++++++++++--- That's more than half the existing driver... > 1 file changed, 226 insertions(+), 21 deletions(-) > > diff --git a/drivers/watchdog/sbsa_gwdt.c b/drivers/watchdog/sbsa_gwdt.c > index 5f23913ce3b4..0bc6f53f0968 100644 > --- a/drivers/watchdog/sbsa_gwdt.c > +++ b/drivers/watchdog/sbsa_gwdt.c > @@ -46,10 +46,13 @@ > #include <linux/mod_devicetable.h> > #include <linux/module.h> > #include <linux/moduleparam.h> > +#include <linux/of.h> > +#include <linux/of_device.h> > #include <linux/platform_device.h> > #include <linux/uaccess.h> > #include <linux/watchdog.h> > #include <asm/arch_timer.h> > +#include <linux/arm-smccc.h> > > #define DRV_NAME "sbsa-gwdt" > #define WATCHDOG_NAME "SBSA Generic Watchdog" > @@ -75,6 +78,68 @@ > #define SBSA_GWDT_VERSION_MASK 0xF > #define SBSA_GWDT_VERSION_SHIFT 16 > > +/* Marvell AC5/X SMCs, taken from arm trusted firmware */ > +#define SMC_FID_READ_REG 0x80007FFE > +#define SMC_FID_WRITE_REG 0x80007FFD > + > +/* Marvell registers offsets: */ > +#define SBSA_GWDT_MARVELL_CPU_WD_RST_EN_REG 0x30 > +#define SBSA_GWDT_MARVELL_MNG_ID_REG 0x4C > +#define SBSA_GWDT_MARVELL_RST_CTRL_REG 0x0C > + > +#define SBSA_GWDT_MARVELL_ID_MASK GENMASK(19, 12) > +#define SBSA_GWDT_MARVELL_AC5_ID 0xB4000 > +#define SBSA_GWDT_MARVELL_AC5X_ID 0x98000 > +#define SBSA_GWDT_MARVELL_IML_ID 0xA0000 > +#define SBSA_GWDT_MARVELL_IMM_ID 0xA2000 > + > +#define SBSA_GWDT_MARVELL_AC5_RST_UNIT_WD_BIT BIT(6) > +/* The following applies to AC5X, IronMan L and M: */ > +#define SBSA_GWDT_MARVELL_IRONMAN_RST_UNIT_WD_BIT BIT(7) > + > +/* > + * Action to perform after watchdog gets WS1 (watchdog signal 1) interrupt > + * PWD = Private Watchdog, GWD - Global Watchdog, mpp - multi purpose pin > + * > + * 0 = Enable 1 = Disable (Default) > + * > + * BIT 0: CPU 0 reset by PWD 0 > + * BIT 1: CPU 1 reset by PWD 1 > + * BIT 2: CPU 0 reset by GWD > + * BIT 3: CPU 1 reset by GWD > + * BIT 4: PWD 0 sys reset out > + * BIT 5: PWD 1 sys reset out > + * BIT 6: GWD sys reset out > + * BIT 7: Reserved > + * BIT 8: PWD 0 mpp reset out > + * BIT 9: PWD 1 mpp reset out > + * BIT 10: GWD mpp reset out > + * > + */ > +#define SBSA_GWDT_MARVELL_RST_CPU0_BY_PWD0 BIT(0) > +#define SBSA_GWDT_MARVELL_RST_CPU1_BY_PWD1 BIT(1) > +#define SBSA_GWDT_MARVELL_RST_CPU0_BY_GWD BIT(2) > +#define SBSA_GWDT_MARVELL_RST_CPU1_BY_GWD BIT(3) > +#define SBSA_GWDT_MARVELL_RST_SYSRST_BY_PWD0 BIT(4) > +#define SBSA_GWDT_MARVELL_RST_SYSRST_BY_PWD1 BIT(5) > +#define SBSA_GWDT_MARVELL_RST_SYSRST_BY_GWD BIT(6) > +#define SBSA_GWDT_MARVELL_RST_RESERVED BIT(7) > +#define SBSA_GWDT_MARVELL_RST_MPP_BY_PWD0 BIT(8) > +#define SBSA_GWDT_MARVELL_RST_MPP_BY_PWD1 BIT(9) > +#define SBSA_GWDT_MARVELL_RST_MPP_BY_GWD BIT(10) > + > +/** > + * struct sbsa_gwdt_regs_ops - ops for register read/write, depending on SOC > + * @reg_read: register read ops function > + * @read_write: register write ops function > + */ > +struct sbsa_gwdt_regs_ops { > + u32 (*reg_read32)(void __iomem *ptr); > + __u64 (*reg_read64)(void __iomem *ptr); > + void (*reg_write32)(u32 val, void __iomem *ptr); > + void (*reg_write64)(__u64 val, void __iomem *ptr); > +}; > + > /** > * struct sbsa_gwdt - Internal representation of the SBSA GWDT > * @wdd: kernel watchdog_device structure > @@ -89,6 +154,7 @@ struct sbsa_gwdt { > int version; > void __iomem *refresh_base; > void __iomem *control_base; > + const struct sbsa_gwdt_regs_ops *soc_reg_ops; > }; > > #define DEFAULT_TIMEOUT 10 /* seconds */ > @@ -116,6 +182,91 @@ MODULE_PARM_DESC(nowayout, > "Watchdog cannot be stopped once started (default=" > __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); > > +/* > + * By default, have the Global watchdog cause System Reset: > + */ > +static unsigned int reset = 0xFFFFFFFF ^ SBSA_GWDT_MARVELL_RST_SYSRST_BY_GWD; > +module_param(reset, uint, 0); > +MODULE_PARM_DESC(reset, "Action to perform after watchdog gets WS1 interrupt"); > + > +/* > + * Marvell AC5/X use SMC, while others use direct register access > + */ > +static u32 sbsa_gwdt_smc_readl(void __iomem *addr) > +{ > + struct arm_smccc_res smc_res; > + > + arm_smccc_smc(SMC_FID_READ_REG, (unsigned long)addr, > + 0, 0, 0, 0, 0, 0, &smc_res); > + return (u32)smc_res.a0; > +} > + > +static void sbsa_gwdt_smc_writel(u32 val, void __iomem *addr) > +{ > + struct arm_smccc_res smc_res; > + > + arm_smccc_smc(SMC_FID_WRITE_REG, (unsigned long)addr, > + (unsigned long)val, 0, 0, 0, 0, 0, &smc_res); > +} > + > +static inline u64 sbsa_gwdt_lo_hi_smc_readq(void __iomem *addr) > +{ > + u32 low, high; > + > + low = sbsa_gwdt_smc_readl(addr); > + high = sbsa_gwdt_smc_readl(addr + 4); > + /* read twice, as a workaround to HW limitation */ > + low = sbsa_gwdt_smc_readl(addr); > + > + return low + ((u64)high << 32); > +} > + > +static inline void sbsa_gwdt_lo_hi_smc_writeq(__u64 val, void __iomem *addr) > +{ > + u32 low, high; > + > + low = val & 0xffffffff; > + high = val >> 32; > + sbsa_gwdt_smc_writel(low, addr); > + sbsa_gwdt_smc_writel(high, addr + 4); > + /* write twice, as a workaround to HW limitation */ > + sbsa_gwdt_smc_writel(low, addr); > +} > + > +static u32 sbsa_gwdt_direct_reg_readl(void __iomem *addr) > +{ > + return readl(addr); > +} > + > +static void sbsa_gwdt_direct_reg_writel(u32 val, void __iomem *addr) > +{ > + writel(val, addr); > +} > + > +static inline u64 sbsa_gwdt_lo_hi_direct_readq(void __iomem *addr) > +{ > + return lo_hi_readq(addr); > +} > + > +static inline void sbsa_gwdt_lo_hi_direct_writeq(__u64 val, void __iomem *addr) > +{ > + lo_hi_writeq(val, addr); > +} > + > +static const struct sbsa_gwdt_regs_ops smc_reg_ops = { > + .reg_read32 = sbsa_gwdt_smc_readl, > + .reg_read64 = sbsa_gwdt_lo_hi_smc_readq, > + .reg_write32 = sbsa_gwdt_smc_writel, > + .reg_write64 = sbsa_gwdt_lo_hi_smc_writeq > +}; > + > +static const struct sbsa_gwdt_regs_ops direct_reg_ops = { > + .reg_read32 = sbsa_gwdt_direct_reg_readl, > + .reg_read64 = sbsa_gwdt_lo_hi_direct_readq, > + .reg_write32 = sbsa_gwdt_direct_reg_writel, > + .reg_write64 = sbsa_gwdt_lo_hi_smc_writeq > +}; The watchdog_ops are already practically not much more than a register read or write. Do we really need 2 levels of ops here? > + > /* > * Arm Base System Architecture 1.0 introduces watchdog v1 which > * increases the length watchdog offset register to 48 bits. > @@ -127,17 +278,17 @@ MODULE_PARM_DESC(nowayout, > static u64 sbsa_gwdt_reg_read(struct sbsa_gwdt *gwdt) > { > if (gwdt->version == 0) > - return readl(gwdt->control_base + SBSA_GWDT_WOR); > + return gwdt->soc_reg_ops->reg_read32(gwdt->control_base + SBSA_GWDT_WOR); > else > - return lo_hi_readq(gwdt->control_base + SBSA_GWDT_WOR); > + return gwdt->soc_reg_ops->reg_read64(gwdt->control_base + SBSA_GWDT_WOR); > } Here we already have a different way to abstract register accesses. Probably should have something that works for all 3 cases... Rob