diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-23 13:28:57 -0700 |
| commit | 4352b8aee98005853aa63f57d6377282de17a33f (patch) | |
| tree | 27614d7a9ef05c32d089004e104364ce42324837 /drivers/rtc/rtc-m48t86.c | |
| download | linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.tar.gz linux-stable-4352b8aee98005853aa63f57d6377282de17a33f.zip | |
Merge tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linuxgrafted
Pull I3C updates from Alexandre Belloni:
"A new driver this cycle, for the AMD AXI I3C controller.
There are also new features such as IBI wakeup support, SETAASA device
discovery and ACPI support for the the DAA methods, meaning we can now
communicate with the SPD devices on DDR5 modules.
New driver:
- AMD AXI I3C master controller
Subsystem:
- I3C Common Command Code (CCC) handling improvements, especially
around GET CCCs
- SETAASA device discovery support
- ACPI support for all existing DAA methods like SETDASA, SETNEWDA as
well as I2C devices on I3C bus
- IBI-based wakeup support
Drivers:
- dw: SETAASA support
- mipi-i3c-hci: advertise IBI wakeup capability, AMD_PT I3C
controller support, PIO queue management support for HCI v1.2
- renesas: improve suspend to RAM support, add runtime PM support"
* tag 'i3c/for-7.3' of git://git.kernel.org/pub/scm/linux/kernel/git/i3c/linux: (70 commits)
i3c: dw: reduce do_daa time if there's no client
i3c: mipi-i3c-hci: Add support for AMD_PT I3C controller
i3c: mipi-i3c-hci: Add PIO queue management support for HCI v1.2
i3c: mipi-i3c-hci: Fix missing STAT_IBI_STATUS_THLD in PIO mode
i3c: dw: rename "pclk" to "apb" to match dt-binding
i3c: dw: make struct dw_i3c_cmd smaller
i3c: dw: use COMMAND_PORT_TRANSFER_ARG instead of hardcoding
i3c: renesas: Don't register devices when ENTDAA times out
i3c: master: dw-i3c-master: fix OD timing for first broadcast
i3c: mipi-i3c-hci: Advertise IBI wakeup capability
i3c: mipi-i3c-hci: Factor out i3c_hci_sysdev()
i3c: mipi-i3c-hci-pci: Propagate I3C wakeup requirements to PCI
i3c: master: Reject IBI requests from non-IBI-capable devices
i3c: master: Add helper to query bus wakeup requirements
i3c: master: Report wakeup events for IBIs
i3c: master: Support IBI-based wakeup capability
i3c: master: Fix potential UAF in i3c_device_match()
i3c: master: Fix potential UAF in i3c_device_uevent()
i3c: Make dev->desc locking assumptions explicit
i3c: master: Fix use-after-free of master->this
...
Diffstat (limited to 'drivers/rtc/rtc-m48t86.c')
| -rw-r--r-- | drivers/rtc/rtc-m48t86.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/drivers/rtc/rtc-m48t86.c b/drivers/rtc/rtc-m48t86.c new file mode 100644 index 000000000..db3bdb171 --- /dev/null +++ b/drivers/rtc/rtc-m48t86.c @@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ST M48T86 / Dallas DS12887 RTC driver + * Copyright (c) 2006 Tower Technologies + * + * Author: Alessandro Zummo <a.zummo@towertech.it> + * + * This drivers only supports the clock running in BCD and 24H mode. + * If it will be ever adapted to binary and 12H mode, care must be taken + * to not introduce bugs. + */ + +#include <linux/module.h> +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/bcd.h> +#include <linux/io.h> + +#define M48T86_SEC 0x00 +#define M48T86_SECALRM 0x01 +#define M48T86_MIN 0x02 +#define M48T86_MINALRM 0x03 +#define M48T86_HOUR 0x04 +#define M48T86_HOURALRM 0x05 +#define M48T86_DOW 0x06 /* 1 = sunday */ +#define M48T86_DOM 0x07 +#define M48T86_MONTH 0x08 /* 1 - 12 */ +#define M48T86_YEAR 0x09 /* 0 - 99 */ +#define M48T86_A 0x0a +#define M48T86_B 0x0b +#define M48T86_B_SET BIT(7) +#define M48T86_B_DM BIT(2) +#define M48T86_B_H24 BIT(1) +#define M48T86_C 0x0c +#define M48T86_D 0x0d +#define M48T86_D_VRT BIT(7) +#define M48T86_NVRAM(x) (0x0e + (x)) +#define M48T86_NVRAM_LEN 114 + +struct m48t86_rtc_info { + void __iomem *index_reg; + void __iomem *data_reg; +}; + +static unsigned char m48t86_readb(struct device *dev, unsigned long addr) +{ + struct m48t86_rtc_info *info = dev_get_drvdata(dev); + unsigned char value; + + writeb(addr, info->index_reg); + value = readb(info->data_reg); + + return value; +} + +static void m48t86_writeb(struct device *dev, + unsigned char value, unsigned long addr) +{ + struct m48t86_rtc_info *info = dev_get_drvdata(dev); + + writeb(addr, info->index_reg); + writeb(value, info->data_reg); +} + +static int m48t86_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char reg; + + reg = m48t86_readb(dev, M48T86_B); + + if (reg & M48T86_B_DM) { + /* data (binary) mode */ + tm->tm_sec = m48t86_readb(dev, M48T86_SEC); + tm->tm_min = m48t86_readb(dev, M48T86_MIN); + tm->tm_hour = m48t86_readb(dev, M48T86_HOUR) & 0x3f; + tm->tm_mday = m48t86_readb(dev, M48T86_DOM); + /* tm_mon is 0-11 */ + tm->tm_mon = m48t86_readb(dev, M48T86_MONTH) - 1; + tm->tm_year = m48t86_readb(dev, M48T86_YEAR) + 100; + tm->tm_wday = m48t86_readb(dev, M48T86_DOW); + } else { + /* bcd mode */ + tm->tm_sec = bcd2bin(m48t86_readb(dev, M48T86_SEC)); + tm->tm_min = bcd2bin(m48t86_readb(dev, M48T86_MIN)); + tm->tm_hour = bcd2bin(m48t86_readb(dev, M48T86_HOUR) & + 0x3f); + tm->tm_mday = bcd2bin(m48t86_readb(dev, M48T86_DOM)); + /* tm_mon is 0-11 */ + tm->tm_mon = bcd2bin(m48t86_readb(dev, M48T86_MONTH)) - 1; + tm->tm_year = bcd2bin(m48t86_readb(dev, M48T86_YEAR)) + 100; + tm->tm_wday = bcd2bin(m48t86_readb(dev, M48T86_DOW)); + } + + /* correct the hour if the clock is in 12h mode */ + if (!(reg & M48T86_B_H24)) + if (m48t86_readb(dev, M48T86_HOUR) & 0x80) + tm->tm_hour += 12; + + return 0; +} + +static int m48t86_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + unsigned char reg; + + reg = m48t86_readb(dev, M48T86_B); + + /* update flag and 24h mode */ + reg |= M48T86_B_SET | M48T86_B_H24; + m48t86_writeb(dev, reg, M48T86_B); + + if (reg & M48T86_B_DM) { + /* data (binary) mode */ + m48t86_writeb(dev, tm->tm_sec, M48T86_SEC); + m48t86_writeb(dev, tm->tm_min, M48T86_MIN); + m48t86_writeb(dev, tm->tm_hour, M48T86_HOUR); + m48t86_writeb(dev, tm->tm_mday, M48T86_DOM); + m48t86_writeb(dev, tm->tm_mon + 1, M48T86_MONTH); + m48t86_writeb(dev, tm->tm_year % 100, M48T86_YEAR); + m48t86_writeb(dev, tm->tm_wday, M48T86_DOW); + } else { + /* bcd mode */ + m48t86_writeb(dev, bin2bcd(tm->tm_sec), M48T86_SEC); + m48t86_writeb(dev, bin2bcd(tm->tm_min), M48T86_MIN); + m48t86_writeb(dev, bin2bcd(tm->tm_hour), M48T86_HOUR); + m48t86_writeb(dev, bin2bcd(tm->tm_mday), M48T86_DOM); + m48t86_writeb(dev, bin2bcd(tm->tm_mon + 1), M48T86_MONTH); + m48t86_writeb(dev, bin2bcd(tm->tm_year % 100), M48T86_YEAR); + m48t86_writeb(dev, bin2bcd(tm->tm_wday), M48T86_DOW); + } + + /* update ended */ + reg &= ~M48T86_B_SET; + m48t86_writeb(dev, reg, M48T86_B); + + return 0; +} + +static int m48t86_rtc_proc(struct device *dev, struct seq_file *seq) +{ + unsigned char reg; + + reg = m48t86_readb(dev, M48T86_B); + + seq_printf(seq, "mode\t\t: %s\n", + (reg & M48T86_B_DM) ? "binary" : "bcd"); + + reg = m48t86_readb(dev, M48T86_D); + + seq_printf(seq, "battery\t\t: %s\n", + (reg & M48T86_D_VRT) ? "ok" : "exhausted"); + + return 0; +} + +static const struct rtc_class_ops m48t86_rtc_ops = { + .read_time = m48t86_rtc_read_time, + .set_time = m48t86_rtc_set_time, + .proc = m48t86_rtc_proc, +}; + +static int m48t86_nvram_read(void *priv, unsigned int off, void *buf, + size_t count) +{ + struct device *dev = priv; + unsigned int i; + + for (i = 0; i < count; i++) + ((u8 *)buf)[i] = m48t86_readb(dev, M48T86_NVRAM(off + i)); + + return 0; +} + +static int m48t86_nvram_write(void *priv, unsigned int off, void *buf, + size_t count) +{ + struct device *dev = priv; + unsigned int i; + + for (i = 0; i < count; i++) + m48t86_writeb(dev, ((u8 *)buf)[i], M48T86_NVRAM(off + i)); + + return 0; +} + +/* + * The RTC is an optional feature at purchase time on some Technologic Systems + * boards. Verify that it actually exists by checking if the last two bytes + * of the NVRAM can be changed. + * + * This is based on the method used in their rtc7800.c example. + */ +static bool m48t86_verify_chip(struct platform_device *pdev) +{ + unsigned int offset0 = M48T86_NVRAM(M48T86_NVRAM_LEN - 2); + unsigned int offset1 = M48T86_NVRAM(M48T86_NVRAM_LEN - 1); + unsigned char tmp0, tmp1; + + tmp0 = m48t86_readb(&pdev->dev, offset0); + tmp1 = m48t86_readb(&pdev->dev, offset1); + + m48t86_writeb(&pdev->dev, 0x00, offset0); + m48t86_writeb(&pdev->dev, 0x55, offset1); + if (m48t86_readb(&pdev->dev, offset1) == 0x55) { + m48t86_writeb(&pdev->dev, 0xaa, offset1); + if (m48t86_readb(&pdev->dev, offset1) == 0xaa && + m48t86_readb(&pdev->dev, offset0) == 0x00) { + m48t86_writeb(&pdev->dev, tmp0, offset0); + m48t86_writeb(&pdev->dev, tmp1, offset1); + + return true; + } + } + return false; +} + +static int m48t86_rtc_probe(struct platform_device *pdev) +{ + struct m48t86_rtc_info *info; + struct rtc_device *rtc; + unsigned char reg; + int err; + struct nvmem_config m48t86_nvmem_cfg = { + .name = "m48t86_nvram", + .word_size = 1, + .stride = 1, + .size = M48T86_NVRAM_LEN, + .reg_read = m48t86_nvram_read, + .reg_write = m48t86_nvram_write, + .priv = &pdev->dev, + }; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->index_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(info->index_reg)) + return PTR_ERR(info->index_reg); + + info->data_reg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(info->data_reg)) + return PTR_ERR(info->data_reg); + + dev_set_drvdata(&pdev->dev, info); + + if (!m48t86_verify_chip(pdev)) { + dev_info(&pdev->dev, "RTC not present\n"); + return -ENODEV; + } + + rtc = devm_rtc_allocate_device(&pdev->dev); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + + rtc->ops = &m48t86_rtc_ops; + + err = devm_rtc_register_device(rtc); + if (err) + return err; + + devm_rtc_nvmem_register(rtc, &m48t86_nvmem_cfg); + + /* read battery status */ + reg = m48t86_readb(&pdev->dev, M48T86_D); + dev_info(&pdev->dev, "battery %s\n", + (reg & M48T86_D_VRT) ? "ok" : "exhausted"); + + return 0; +} + +static const struct of_device_id m48t86_rtc_of_ids[] = { + { .compatible = "st,m48t86" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, m48t86_rtc_of_ids); + +static struct platform_driver m48t86_rtc_platform_driver = { + .driver = { + .name = "rtc-m48t86", + .of_match_table = m48t86_rtc_of_ids, + }, + .probe = m48t86_rtc_probe, +}; + +module_platform_driver(m48t86_rtc_platform_driver); + +MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); +MODULE_DESCRIPTION("M48T86 RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:rtc-m48t86"); |
