// SPDX-License-Identifier: GPL-2.0+ /* * rtc-dm355evm.c - access battery-backed counter in MSP430 firmware * * Copyright (c) 2008 by David Brownell */ #include <linux/kernel.h> #include <linux/init.h> #include <linux/rtc.h> #include <linux/platform_device.h> #include <linux/mfd/dm355evm_msp.h> #include <linux/module.h> /* * The MSP430 firmware on the DM355 EVM uses a watch crystal to feed * a 1 Hz counter. When a backup battery is supplied, that makes a * reasonable RTC for applications where alarms and non-NTP drift * compensation aren't important. * * The only real glitch is the inability to read or write all four * counter bytes atomically: the count may increment in the middle * of an operation, causing trouble when the LSB rolls over. * * This driver was tested with firmware revision A4. */ union evm_time { u8 bytes[4]; u32 value; }; static int dm355evm_rtc_read_time(struct device *dev, struct rtc_time *tm) { union evm_time time; int status; int tries = 0; do { /* * Read LSB(0) to MSB(3) bytes. Defend against the counter * rolling over by re-reading until the value is stable, * and assuming the four reads take at most a few seconds. */ status = dm355evm_msp_read(DM355EVM_MSP_RTC_0); if (status < 0) return status; if (tries && time.bytes[0] == status) break; time.bytes[0] = status; status = dm355evm_msp_read(DM355EVM_MSP_RTC_1); if (status < 0) return status; if (tries && time.bytes[1] == status) break; time.bytes[1] = status; status = dm355evm_msp_read(DM355EVM_MSP_RTC_2); if (status < 0) return status; if (tries && time.bytes[2] == status) break; time.bytes[2] = status; status = dm355evm_msp_read(DM355EVM_MSP_RTC_3); if (status < 0) return status; if (tries && time.bytes[3] == status) break; time.bytes[3] = status; } while (++tries < 5); dev_dbg(dev, "read timestamp %08x\n", time.value); rtc_time64_to_tm(le32_to_cpu(time.value), tm); return 0; } static int dm355evm_rtc_set_time(struct device *dev, struct rtc_time *tm) { union evm_time time; unsigned long value; int status; value = rtc_tm_to_time64(tm); time.value = cpu_to_le32(value); dev_dbg(dev, "write timestamp %08x\n", time.value); /* * REVISIT handle non-atomic writes ... maybe just retry until * byte[1] sticks (no rollover)? */ status = dm355evm_msp_write(time.bytes[0], DM355EVM_MSP_RTC_0); if (status < 0) return status; status = dm355evm_msp_write(time.bytes[1], DM355EVM_MSP_RTC_1); if (status < 0) return status; status = dm355evm_msp_write(time.bytes[2], DM355EVM_MSP_RTC_2); if (status < 0) return status; status = dm355evm_msp_write(time.bytes[3], DM355EVM_MSP_RTC_3); if (status < 0) return status; return 0; } static const struct rtc_class_ops dm355evm_rtc_ops = { .read_time = dm355evm_rtc_read_time, .set_time = dm355evm_rtc_set_time, }; /*----------------------------------------------------------------------*/ static int dm355evm_rtc_probe(struct platform_device *pdev) { struct rtc_device *rtc; rtc = devm_rtc_allocate_device(&pdev->dev); if (IS_ERR(rtc)) return PTR_ERR(rtc); platform_set_drvdata(pdev, rtc); rtc->ops = &dm355evm_rtc_ops; rtc->range_max = U32_MAX; return devm_rtc_register_device(rtc); } /* * I2C is used to talk to the MSP430, but this platform device is * exposed by an MFD driver that manages I2C communications. */ static struct platform_driver rtc_dm355evm_driver = { .probe = dm355evm_rtc_probe, .driver = { .name = "rtc-dm355evm", }, }; module_platform_driver(rtc_dm355evm_driver); MODULE_LICENSE("GPL");