diff mbox series

[v4,1/6] rtc: ls2x: Add support for the Loongson-2K/LS7A RTC

Message ID 20210628164552.1006079-2-git@xen0n.name
State Superseded
Headers show
Series rtc: ls2x: Add support for the Loongson-2K/LS7A RTC | expand

Commit Message

WANG Xuerui June 28, 2021, 4:45 p.m. UTC
This RTC module is integrated into the Loongson-2K SoC and the LS7A
bridge chip. This version is almost entirely rewritten to make use of
current kernel API.

Signed-off-by: Huacai Chen <chenhuacai@kernel.org>
Signed-off-by: WANG Xuerui <git@xen0n.name>
Tested-by: Jiaxun Yang <jiaxun.yang@flygoat.com> # loongson2k
Cc: devicetree@vger.kernel.org
Cc: linux-mips@vger.kernel.org
---
 drivers/rtc/Kconfig    |  11 +++
 drivers/rtc/Makefile   |   1 +
 drivers/rtc/rtc-ls2x.c | 184 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 196 insertions(+)
 create mode 100644 drivers/rtc/rtc-ls2x.c

Comments

Nobuhiro Iwamatsu July 2, 2021, 5:52 a.m. UTC | #1
Hi,

2021年6月29日(火) 1:52 WANG Xuerui <git@xen0n.name>:
>
> This RTC module is integrated into the Loongson-2K SoC and the LS7A
> bridge chip. This version is almost entirely rewritten to make use of
> current kernel API.
>
> Signed-off-by: Huacai Chen <chenhuacai@kernel.org>
> Signed-off-by: WANG Xuerui <git@xen0n.name>
> Tested-by: Jiaxun Yang <jiaxun.yang@flygoat.com> # loongson2k
> Cc: devicetree@vger.kernel.org
> Cc: linux-mips@vger.kernel.org
> ---
>  drivers/rtc/Kconfig    |  11 +++
>  drivers/rtc/Makefile   |   1 +
>  drivers/rtc/rtc-ls2x.c | 184 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 196 insertions(+)
>  create mode 100644 drivers/rtc/rtc-ls2x.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 12153d5801ce..253b9f22a162 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -1304,6 +1304,17 @@ config RTC_DRV_NTXEC
>           embedded controller found in certain e-book readers designed by the
>           original design manufacturer Netronix.
>
> +config RTC_DRV_LS2X
> +       tristate "Loongson LS2X RTC"
> +       depends on MACH_LOONGSON64 || COMPILE_TEST
> +       select REGMAP_MMIO
> +       help
> +         If you say yes here you get support for the RTC on the Loongson-2K
> +         SoC and LS7A bridge, which first appeared on the Loongson-2H.
> +
> +         This driver can also be built as a module. If so, the module
> +         will be called rtc-ls2x.
> +
>  comment "on-CPU RTC drivers"
>
>  config RTC_DRV_ASM9260
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 2dd0dd956b0e..6042ff1e73b7 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -81,6 +81,7 @@ obj-$(CONFIG_RTC_DRV_LOONGSON1)       += rtc-ls1x.o
>  obj-$(CONFIG_RTC_DRV_LP8788)   += rtc-lp8788.o
>  obj-$(CONFIG_RTC_DRV_LPC24XX)  += rtc-lpc24xx.o
>  obj-$(CONFIG_RTC_DRV_LPC32XX)  += rtc-lpc32xx.o
> +obj-$(CONFIG_RTC_DRV_LS2X)     += rtc-ls2x.o
>  obj-$(CONFIG_RTC_DRV_M41T80)   += rtc-m41t80.o
>  obj-$(CONFIG_RTC_DRV_M41T93)   += rtc-m41t93.o
>  obj-$(CONFIG_RTC_DRV_M41T94)   += rtc-m41t94.o
> diff --git a/drivers/rtc/rtc-ls2x.c b/drivers/rtc/rtc-ls2x.c
> new file mode 100644
> index 000000000000..1147a6e1591f
> --- /dev/null
> +++ b/drivers/rtc/rtc-ls2x.c
> @@ -0,0 +1,184 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Loongson-2K/7A RTC driver
> + *
> + * Based on the original out-of-tree Loongson-2H RTC driver for Linux 2.6.32,
> + * by Shaozong Liu <liushaozong@loongson.cn>.
> + *
> + * Maintained out-of-tree by Huacai Chen <chenhuacai@kernel.org>.
> + *
> + * Rewritten for mainline by WANG Xuerui <git@xen0n.name>.
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/regmap.h>
> +#include <linux/rtc.h>
> +
> +#define TOY_TRIM_REG   0x20
> +#define TOY_WRITE0_REG 0x24
> +#define TOY_WRITE1_REG 0x28
> +#define TOY_READ0_REG  0x2c
> +#define TOY_READ1_REG  0x30
> +#define TOY_MATCH0_REG 0x34
> +#define TOY_MATCH1_REG 0x38
> +#define TOY_MATCH2_REG 0x3c
> +#define RTC_CTRL_REG   0x40
> +#define RTC_TRIM_REG   0x60
> +#define RTC_WRITE0_REG 0x64
> +#define RTC_READ0_REG  0x68
> +#define RTC_MATCH0_REG 0x6c
> +#define RTC_MATCH1_REG 0x70
> +#define RTC_MATCH2_REG 0x74
> +
> +#define TOY_MON        GENMASK(31, 26)
> +#define TOY_DAY        GENMASK(25, 21)
> +#define TOY_HOUR       GENMASK(20, 16)
> +#define TOY_MIN        GENMASK(15, 10)
> +#define TOY_SEC        GENMASK(9, 4)
> +#define TOY_MSEC       GENMASK(3, 0)
> +
> +struct ls2x_rtc_priv {
> +       struct regmap *regmap;
> +};
> +
> +static const struct regmap_config ls2x_rtc_regmap_config = {
> +       .reg_bits = 32,
> +       .val_bits = 32,
> +       .reg_stride = 4,
> +};
> +
> +struct ls2x_rtc_regs {
> +       u32 reg0;
> +       u32 reg1;
> +};
> +
> +static inline void ls2x_rtc_regs_to_time(struct ls2x_rtc_regs *regs,
> +                                        struct rtc_time *tm)
> +{
> +       tm->tm_year = regs->reg1;
> +       tm->tm_sec = FIELD_GET(TOY_SEC, regs->reg0);
> +       tm->tm_min = FIELD_GET(TOY_MIN, regs->reg0);
> +       tm->tm_hour = FIELD_GET(TOY_HOUR, regs->reg0);
> +       tm->tm_mday = FIELD_GET(TOY_DAY, regs->reg0);
> +       tm->tm_mon = FIELD_GET(TOY_MON, regs->reg0) - 1;
> +}
> +
> +static inline void ls2x_rtc_time_to_regs(struct rtc_time *tm,
> +                                        struct ls2x_rtc_regs *regs)
> +{
> +       regs->reg0 = FIELD_PREP(TOY_SEC, tm->tm_sec);
> +       regs->reg0 |= FIELD_PREP(TOY_MIN, tm->tm_min);
> +       regs->reg0 |= FIELD_PREP(TOY_HOUR, tm->tm_hour);
> +       regs->reg0 |= FIELD_PREP(TOY_DAY, tm->tm_mday);
> +       regs->reg0 |= FIELD_PREP(TOY_MON, tm->tm_mon + 1);
> +       regs->reg1 = tm->tm_year;
> +}
> +
> +static int ls2x_rtc_read_time(struct device *dev, struct rtc_time *tm)
> +{
> +       struct ls2x_rtc_priv *priv = dev_get_drvdata(dev);
> +       struct ls2x_rtc_regs regs;
> +       int ret;
> +
> +       ret = regmap_read(priv->regmap, TOY_READ1_REG, &regs.reg1);
> +       if (unlikely(ret))
> +               return ret;
> +
> +       ret = regmap_read(priv->regmap, TOY_READ0_REG, &regs.reg0);
> +       if (unlikely(ret))
> +               return ret;
> +
> +       ls2x_rtc_regs_to_time(&regs, tm);
> +
> +       return 0;
> +}
> +
> +static int ls2x_rtc_set_time(struct device *dev, struct rtc_time *tm)
> +{
> +       struct ls2x_rtc_priv *priv = dev_get_drvdata(dev);
> +       struct ls2x_rtc_regs regs;
> +       int ret;
> +
> +       ls2x_rtc_time_to_regs(tm, &regs);
> +
> +       ret = regmap_write(priv->regmap, TOY_WRITE0_REG, regs.reg0);
> +       if (unlikely(ret))
> +               return ret;
> +
> +       ret = regmap_write(priv->regmap, TOY_WRITE1_REG, regs.reg1);
> +       if (unlikely(ret))
> +               return ret;

I think this can be returned directly.

> +
> +       return 0;
> +}
> +
> +static struct rtc_class_ops ls2x_rtc_ops = {
> +       .read_time = ls2x_rtc_read_time,
> +       .set_time = ls2x_rtc_set_time,
> +};
> +
> +static int ls2x_rtc_probe(struct platform_device *pdev)
> +{
> +       struct device *dev = &pdev->dev;
> +       struct rtc_device *rtc;
> +       struct ls2x_rtc_priv *priv;
> +       void __iomem *regs;
> +
> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
> +       if (unlikely(!priv))
> +               return -ENOMEM;
> +
> +       platform_set_drvdata(pdev, priv);
> +
> +       regs = devm_platform_ioremap_resource(pdev, 0);
> +       if (IS_ERR(regs))
> +               return PTR_ERR(regs);
> +
> +       priv->regmap = devm_regmap_init_mmio(dev, regs,
> +                                            &ls2x_rtc_regmap_config);
> +       if (IS_ERR(priv->regmap))
> +               return PTR_ERR(priv->regmap);
> +
> +       rtc = devm_rtc_allocate_device(dev);
> +       if (IS_ERR(rtc))
> +               return PTR_ERR(rtc);
> +
> +       rtc->ops = &ls2x_rtc_ops;
> +
> +       /* Due to hardware erratum, all years multiple of 4 are considered
> +        * leap year, so only years 2000 through 2099 are usable.
> +        *
> +        * Previous out-of-tree versions of this driver wrote tm_year directly
> +        * into the year register, so epoch 2000 must be used to preserve
> +        * semantics on shipped systems.
> +        */
> +       rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
> +       rtc->range_max = RTC_TIMESTAMP_END_2099;
> +
> +       return devm_rtc_register_device(rtc);
> +}
> +
> +static const struct of_device_id ls2x_rtc_of_match[] = {
> +       { .compatible = "loongson,ls2x-rtc" },
> +       { /* sentinel */ },
> +};
> +MODULE_DEVICE_TABLE(of, ls2x_rtc_of_match);
> +
> +static struct platform_driver ls2x_rtc_driver = {
> +       .probe          = ls2x_rtc_probe,
> +       .driver         = {
> +               .name   = "ls2x-rtc",
> +               .of_match_table = of_match_ptr(ls2x_rtc_of_match),

If my understanding is correct, this architecture is a DT-only platform.
Therefore, of_match_ptr() macro is unnecessary.

> +       },
> +};
> +
> +module_platform_driver(ls2x_rtc_driver);
> +
> +MODULE_DESCRIPTION("LS2X RTC driver");
> +MODULE_AUTHOR("WANG Xuerui");
> +MODULE_AUTHOR("Huacai Chen");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:ls2x-rtc");
> --
> 2.30.1
>
WANG Xuerui July 2, 2021, 6:16 a.m. UTC | #2
Hi Nobuhiro-san,

(sorry for the duplicated reply on your side; I didn't include all recipients correctly in the first mail, hence the resend.)

On 2021/7/2 13:52, Nobuhiro Iwamatsu wrote:
> Hi,
>
> 2021年6月29日(火) 1:52 WANG Xuerui <git@xen0n.name>:
>> This RTC module is integrated into the Loongson-2K SoC and the LS7A
>> bridge chip. This version is almost entirely rewritten to make use of
>> current kernel API.
>>
>> Signed-off-by: Huacai Chen <chenhuacai@kernel.org>
>> Signed-off-by: WANG Xuerui <git@xen0n.name>
>> Tested-by: Jiaxun Yang <jiaxun.yang@flygoat.com> # loongson2k
>> Cc: devicetree@vger.kernel.org
>> Cc: linux-mips@vger.kernel.org
>> ---
>>  drivers/rtc/Kconfig    |  11 +++
>>  drivers/rtc/Makefile   |   1 +
>>  drivers/rtc/rtc-ls2x.c | 184 +++++++++++++++++++++++++++++++++++++++++
>>  3 files changed, 196 insertions(+)
>>  create mode 100644 drivers/rtc/rtc-ls2x.c
>>
>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>> index 12153d5801ce..253b9f22a162 100644
>> --- a/drivers/rtc/Kconfig
>> +++ b/drivers/rtc/Kconfig
>> @@ -1304,6 +1304,17 @@ config RTC_DRV_NTXEC
>>           embedded controller found in certain e-book readers designed by the
>>           original design manufacturer Netronix.
>>
>> +config RTC_DRV_LS2X
>> +       tristate "Loongson LS2X RTC"
>> +       depends on MACH_LOONGSON64 || COMPILE_TEST
>> +       select REGMAP_MMIO
>> +       help
>> +         If you say yes here you get support for the RTC on the Loongson-2K
>> +         SoC and LS7A bridge, which first appeared on the Loongson-2H.
>> +
>> +         This driver can also be built as a module. If so, the module
>> +         will be called rtc-ls2x.
>> +
>>  comment "on-CPU RTC drivers"
>>
>>  config RTC_DRV_ASM9260
>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>> index 2dd0dd956b0e..6042ff1e73b7 100644
>> --- a/drivers/rtc/Makefile
>> +++ b/drivers/rtc/Makefile
>> @@ -81,6 +81,7 @@ obj-$(CONFIG_RTC_DRV_LOONGSON1)       += rtc-ls1x.o
>>  obj-$(CONFIG_RTC_DRV_LP8788)   += rtc-lp8788.o
>>  obj-$(CONFIG_RTC_DRV_LPC24XX)  += rtc-lpc24xx.o
>>  obj-$(CONFIG_RTC_DRV_LPC32XX)  += rtc-lpc32xx.o
>> +obj-$(CONFIG_RTC_DRV_LS2X)     += rtc-ls2x.o
>>  obj-$(CONFIG_RTC_DRV_M41T80)   += rtc-m41t80.o
>>  obj-$(CONFIG_RTC_DRV_M41T93)   += rtc-m41t93.o
>>  obj-$(CONFIG_RTC_DRV_M41T94)   += rtc-m41t94.o
>> diff --git a/drivers/rtc/rtc-ls2x.c b/drivers/rtc/rtc-ls2x.c
>> new file mode 100644
>> index 000000000000..1147a6e1591f
>> --- /dev/null
>> +++ b/drivers/rtc/rtc-ls2x.c
>> @@ -0,0 +1,184 @@
>> +// SPDX-License-Identifier: GPL-2.0
>> +/*
>> + * Loongson-2K/7A RTC driver
>> + *
>> + * Based on the original out-of-tree Loongson-2H RTC driver for Linux 2.6.32,
>> + * by Shaozong Liu <liushaozong@loongson.cn>.
>> + *
>> + * Maintained out-of-tree by Huacai Chen <chenhuacai@kernel.org>.
>> + *
>> + * Rewritten for mainline by WANG Xuerui <git@xen0n.name>.
>> + */
>> +
>> +#include <linux/bitfield.h>
>> +#include <linux/module.h>
>> +#include <linux/of.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/regmap.h>
>> +#include <linux/rtc.h>
>> +
>> +#define TOY_TRIM_REG   0x20
>> +#define TOY_WRITE0_REG 0x24
>> +#define TOY_WRITE1_REG 0x28
>> +#define TOY_READ0_REG  0x2c
>> +#define TOY_READ1_REG  0x30
>> +#define TOY_MATCH0_REG 0x34
>> +#define TOY_MATCH1_REG 0x38
>> +#define TOY_MATCH2_REG 0x3c
>> +#define RTC_CTRL_REG   0x40
>> +#define RTC_TRIM_REG   0x60
>> +#define RTC_WRITE0_REG 0x64
>> +#define RTC_READ0_REG  0x68
>> +#define RTC_MATCH0_REG 0x6c
>> +#define RTC_MATCH1_REG 0x70
>> +#define RTC_MATCH2_REG 0x74
>> +
>> +#define TOY_MON        GENMASK(31, 26)
>> +#define TOY_DAY        GENMASK(25, 21)
>> +#define TOY_HOUR       GENMASK(20, 16)
>> +#define TOY_MIN        GENMASK(15, 10)
>> +#define TOY_SEC        GENMASK(9, 4)
>> +#define TOY_MSEC       GENMASK(3, 0)
>> +
>> +struct ls2x_rtc_priv {
>> +       struct regmap *regmap;
>> +};
>> +
>> +static const struct regmap_config ls2x_rtc_regmap_config = {
>> +       .reg_bits = 32,
>> +       .val_bits = 32,
>> +       .reg_stride = 4,
>> +};
>> +
>> +struct ls2x_rtc_regs {
>> +       u32 reg0;
>> +       u32 reg1;
>> +};
>> +
>> +static inline void ls2x_rtc_regs_to_time(struct ls2x_rtc_regs *regs,
>> +                                        struct rtc_time *tm)
>> +{
>> +       tm->tm_year = regs->reg1;
>> +       tm->tm_sec = FIELD_GET(TOY_SEC, regs->reg0);
>> +       tm->tm_min = FIELD_GET(TOY_MIN, regs->reg0);
>> +       tm->tm_hour = FIELD_GET(TOY_HOUR, regs->reg0);
>> +       tm->tm_mday = FIELD_GET(TOY_DAY, regs->reg0);
>> +       tm->tm_mon = FIELD_GET(TOY_MON, regs->reg0) - 1;
>> +}
>> +
>> +static inline void ls2x_rtc_time_to_regs(struct rtc_time *tm,
>> +                                        struct ls2x_rtc_regs *regs)
>> +{
>> +       regs->reg0 = FIELD_PREP(TOY_SEC, tm->tm_sec);
>> +       regs->reg0 |= FIELD_PREP(TOY_MIN, tm->tm_min);
>> +       regs->reg0 |= FIELD_PREP(TOY_HOUR, tm->tm_hour);
>> +       regs->reg0 |= FIELD_PREP(TOY_DAY, tm->tm_mday);
>> +       regs->reg0 |= FIELD_PREP(TOY_MON, tm->tm_mon + 1);
>> +       regs->reg1 = tm->tm_year;
>> +}
>> +
>> +static int ls2x_rtc_read_time(struct device *dev, struct rtc_time *tm)
>> +{
>> +       struct ls2x_rtc_priv *priv = dev_get_drvdata(dev);
>> +       struct ls2x_rtc_regs regs;
>> +       int ret;
>> +
>> +       ret = regmap_read(priv->regmap, TOY_READ1_REG, &regs.reg1);
>> +       if (unlikely(ret))
>> +               return ret;
>> +
>> +       ret = regmap_read(priv->regmap, TOY_READ0_REG, &regs.reg0);
>> +       if (unlikely(ret))
>> +               return ret;
>> +
>> +       ls2x_rtc_regs_to_time(&regs, tm);
>> +
>> +       return 0;
>> +}
>> +
>> +static int ls2x_rtc_set_time(struct device *dev, struct rtc_time *tm)
>> +{
>> +       struct ls2x_rtc_priv *priv = dev_get_drvdata(dev);
>> +       struct ls2x_rtc_regs regs;
>> +       int ret;
>> +
>> +       ls2x_rtc_time_to_regs(tm, &regs);
>> +
>> +       ret = regmap_write(priv->regmap, TOY_WRITE0_REG, regs.reg0);
>> +       if (unlikely(ret))
>> +               return ret;
>> +
>> +       ret = regmap_write(priv->regmap, TOY_WRITE1_REG, regs.reg1);
>> +       if (unlikely(ret))
>> +               return ret;
> I think this can be returned directly.

Fair point; this is done purely for the two writes to be visually
symmetrical. I'll send v5.

>
>> +
>> +       return 0;
>> +}
>> +
>> +static struct rtc_class_ops ls2x_rtc_ops = {
>> +       .read_time = ls2x_rtc_read_time,
>> +       .set_time = ls2x_rtc_set_time,
>> +};
>> +
>> +static int ls2x_rtc_probe(struct platform_device *pdev)
>> +{
>> +       struct device *dev = &pdev->dev;
>> +       struct rtc_device *rtc;
>> +       struct ls2x_rtc_priv *priv;
>> +       void __iomem *regs;
>> +
>> +       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
>> +       if (unlikely(!priv))
>> +               return -ENOMEM;
>> +
>> +       platform_set_drvdata(pdev, priv);
>> +
>> +       regs = devm_platform_ioremap_resource(pdev, 0);
>> +       if (IS_ERR(regs))
>> +               return PTR_ERR(regs);
>> +
>> +       priv->regmap = devm_regmap_init_mmio(dev, regs,
>> +                                            &ls2x_rtc_regmap_config);
>> +       if (IS_ERR(priv->regmap))
>> +               return PTR_ERR(priv->regmap);
>> +
>> +       rtc = devm_rtc_allocate_device(dev);
>> +       if (IS_ERR(rtc))
>> +               return PTR_ERR(rtc);
>> +
>> +       rtc->ops = &ls2x_rtc_ops;
>> +
>> +       /* Due to hardware erratum, all years multiple of 4 are considered
>> +        * leap year, so only years 2000 through 2099 are usable.
>> +        *
>> +        * Previous out-of-tree versions of this driver wrote tm_year directly
>> +        * into the year register, so epoch 2000 must be used to preserve
>> +        * semantics on shipped systems.
>> +        */
>> +       rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
>> +       rtc->range_max = RTC_TIMESTAMP_END_2099;
>> +
>> +       return devm_rtc_register_device(rtc);
>> +}
>> +
>> +static const struct of_device_id ls2x_rtc_of_match[] = {
>> +       { .compatible = "loongson,ls2x-rtc" },
>> +       { /* sentinel */ },
>> +};
>> +MODULE_DEVICE_TABLE(of, ls2x_rtc_of_match);
>> +
>> +static struct platform_driver ls2x_rtc_driver = {
>> +       .probe          = ls2x_rtc_probe,
>> +       .driver         = {
>> +               .name   = "ls2x-rtc",
>> +               .of_match_table = of_match_ptr(ls2x_rtc_of_match),
> If my understanding is correct, this architecture is a DT-only platform.
> Therefore, of_match_ptr() macro is unnecessary.

Actually the forthcoming LoongArch platforms have both ACPI and DT, and
this RTC block would of course be included in these (inside the LS7A
bridge chip). If we remove the of_match_ptr usage now, we would still
have to add back later. However I agree about not doing speculations;
I'll remove that usage in v5.

>> +       },
>> +};
>> +
>> +module_platform_driver(ls2x_rtc_driver);
>> +
>> +MODULE_DESCRIPTION("LS2X RTC driver");
>> +MODULE_AUTHOR("WANG Xuerui");
>> +MODULE_AUTHOR("Huacai Chen");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:ls2x-rtc");
>> --
>> 2.30.1
>>
>
diff mbox series

Patch

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 12153d5801ce..253b9f22a162 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -1304,6 +1304,17 @@  config RTC_DRV_NTXEC
 	  embedded controller found in certain e-book readers designed by the
 	  original design manufacturer Netronix.
 
+config RTC_DRV_LS2X
+	tristate "Loongson LS2X RTC"
+	depends on MACH_LOONGSON64 || COMPILE_TEST
+	select REGMAP_MMIO
+	help
+	  If you say yes here you get support for the RTC on the Loongson-2K
+	  SoC and LS7A bridge, which first appeared on the Loongson-2H.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-ls2x.
+
 comment "on-CPU RTC drivers"
 
 config RTC_DRV_ASM9260
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2dd0dd956b0e..6042ff1e73b7 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -81,6 +81,7 @@  obj-$(CONFIG_RTC_DRV_LOONGSON1)	+= rtc-ls1x.o
 obj-$(CONFIG_RTC_DRV_LP8788)	+= rtc-lp8788.o
 obj-$(CONFIG_RTC_DRV_LPC24XX)	+= rtc-lpc24xx.o
 obj-$(CONFIG_RTC_DRV_LPC32XX)	+= rtc-lpc32xx.o
+obj-$(CONFIG_RTC_DRV_LS2X)	+= rtc-ls2x.o
 obj-$(CONFIG_RTC_DRV_M41T80)	+= rtc-m41t80.o
 obj-$(CONFIG_RTC_DRV_M41T93)	+= rtc-m41t93.o
 obj-$(CONFIG_RTC_DRV_M41T94)	+= rtc-m41t94.o
diff --git a/drivers/rtc/rtc-ls2x.c b/drivers/rtc/rtc-ls2x.c
new file mode 100644
index 000000000000..1147a6e1591f
--- /dev/null
+++ b/drivers/rtc/rtc-ls2x.c
@@ -0,0 +1,184 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Loongson-2K/7A RTC driver
+ *
+ * Based on the original out-of-tree Loongson-2H RTC driver for Linux 2.6.32,
+ * by Shaozong Liu <liushaozong@loongson.cn>.
+ *
+ * Maintained out-of-tree by Huacai Chen <chenhuacai@kernel.org>.
+ *
+ * Rewritten for mainline by WANG Xuerui <git@xen0n.name>.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/rtc.h>
+
+#define TOY_TRIM_REG   0x20
+#define TOY_WRITE0_REG 0x24
+#define TOY_WRITE1_REG 0x28
+#define TOY_READ0_REG  0x2c
+#define TOY_READ1_REG  0x30
+#define TOY_MATCH0_REG 0x34
+#define TOY_MATCH1_REG 0x38
+#define TOY_MATCH2_REG 0x3c
+#define RTC_CTRL_REG   0x40
+#define RTC_TRIM_REG   0x60
+#define RTC_WRITE0_REG 0x64
+#define RTC_READ0_REG  0x68
+#define RTC_MATCH0_REG 0x6c
+#define RTC_MATCH1_REG 0x70
+#define RTC_MATCH2_REG 0x74
+
+#define TOY_MON        GENMASK(31, 26)
+#define TOY_DAY        GENMASK(25, 21)
+#define TOY_HOUR       GENMASK(20, 16)
+#define TOY_MIN        GENMASK(15, 10)
+#define TOY_SEC        GENMASK(9, 4)
+#define TOY_MSEC       GENMASK(3, 0)
+
+struct ls2x_rtc_priv {
+	struct regmap *regmap;
+};
+
+static const struct regmap_config ls2x_rtc_regmap_config = {
+	.reg_bits = 32,
+	.val_bits = 32,
+	.reg_stride = 4,
+};
+
+struct ls2x_rtc_regs {
+	u32 reg0;
+	u32 reg1;
+};
+
+static inline void ls2x_rtc_regs_to_time(struct ls2x_rtc_regs *regs,
+					 struct rtc_time *tm)
+{
+	tm->tm_year = regs->reg1;
+	tm->tm_sec = FIELD_GET(TOY_SEC, regs->reg0);
+	tm->tm_min = FIELD_GET(TOY_MIN, regs->reg0);
+	tm->tm_hour = FIELD_GET(TOY_HOUR, regs->reg0);
+	tm->tm_mday = FIELD_GET(TOY_DAY, regs->reg0);
+	tm->tm_mon = FIELD_GET(TOY_MON, regs->reg0) - 1;
+}
+
+static inline void ls2x_rtc_time_to_regs(struct rtc_time *tm,
+					 struct ls2x_rtc_regs *regs)
+{
+	regs->reg0 = FIELD_PREP(TOY_SEC, tm->tm_sec);
+	regs->reg0 |= FIELD_PREP(TOY_MIN, tm->tm_min);
+	regs->reg0 |= FIELD_PREP(TOY_HOUR, tm->tm_hour);
+	regs->reg0 |= FIELD_PREP(TOY_DAY, tm->tm_mday);
+	regs->reg0 |= FIELD_PREP(TOY_MON, tm->tm_mon + 1);
+	regs->reg1 = tm->tm_year;
+}
+
+static int ls2x_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	struct ls2x_rtc_priv *priv = dev_get_drvdata(dev);
+	struct ls2x_rtc_regs regs;
+	int ret;
+
+	ret = regmap_read(priv->regmap, TOY_READ1_REG, &regs.reg1);
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_read(priv->regmap, TOY_READ0_REG, &regs.reg0);
+	if (unlikely(ret))
+		return ret;
+
+	ls2x_rtc_regs_to_time(&regs, tm);
+
+	return 0;
+}
+
+static int ls2x_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct ls2x_rtc_priv *priv = dev_get_drvdata(dev);
+	struct ls2x_rtc_regs regs;
+	int ret;
+
+	ls2x_rtc_time_to_regs(tm, &regs);
+
+	ret = regmap_write(priv->regmap, TOY_WRITE0_REG, regs.reg0);
+	if (unlikely(ret))
+		return ret;
+
+	ret = regmap_write(priv->regmap, TOY_WRITE1_REG, regs.reg1);
+	if (unlikely(ret))
+		return ret;
+
+	return 0;
+}
+
+static struct rtc_class_ops ls2x_rtc_ops = {
+	.read_time = ls2x_rtc_read_time,
+	.set_time = ls2x_rtc_set_time,
+};
+
+static int ls2x_rtc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct rtc_device *rtc;
+	struct ls2x_rtc_priv *priv;
+	void __iomem *regs;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (unlikely(!priv))
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, priv);
+
+	regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(regs))
+		return PTR_ERR(regs);
+
+	priv->regmap = devm_regmap_init_mmio(dev, regs,
+					     &ls2x_rtc_regmap_config);
+	if (IS_ERR(priv->regmap))
+		return PTR_ERR(priv->regmap);
+
+	rtc = devm_rtc_allocate_device(dev);
+	if (IS_ERR(rtc))
+		return PTR_ERR(rtc);
+
+	rtc->ops = &ls2x_rtc_ops;
+
+	/* Due to hardware erratum, all years multiple of 4 are considered
+	 * leap year, so only years 2000 through 2099 are usable.
+	 *
+	 * Previous out-of-tree versions of this driver wrote tm_year directly
+	 * into the year register, so epoch 2000 must be used to preserve
+	 * semantics on shipped systems.
+	 */
+	rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
+	rtc->range_max = RTC_TIMESTAMP_END_2099;
+
+	return devm_rtc_register_device(rtc);
+}
+
+static const struct of_device_id ls2x_rtc_of_match[] = {
+	{ .compatible = "loongson,ls2x-rtc" },
+	{ /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ls2x_rtc_of_match);
+
+static struct platform_driver ls2x_rtc_driver = {
+	.probe		= ls2x_rtc_probe,
+	.driver		= {
+		.name	= "ls2x-rtc",
+		.of_match_table = of_match_ptr(ls2x_rtc_of_match),
+	},
+};
+
+module_platform_driver(ls2x_rtc_driver);
+
+MODULE_DESCRIPTION("LS2X RTC driver");
+MODULE_AUTHOR("WANG Xuerui");
+MODULE_AUTHOR("Huacai Chen");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ls2x-rtc");