| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * |
| * Driver for ST M41T93 SPI RTC |
| * |
| * (c) 2010 Nikolaus Voss, Weinmann Medical GmbH |
| */ |
| |
| #include <linux/bcd.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/rtc.h> |
| #include <linux/spi/spi.h> |
| #include <linux/regmap.h> |
| #include <linux/clk-provider.h> |
| #include <linux/watchdog.h> |
| |
| #define M41T93_REG_SSEC 0 |
| #define M41T93_REG_ST_SEC 1 |
| #define M41T93_REG_MIN 2 |
| #define M41T93_REG_CENT_HOUR 3 |
| #define M41T93_REG_WDAY 4 |
| #define M41T93_REG_DAY 5 |
| #define M41T93_REG_MON 6 |
| #define M41T93_REG_YEAR 7 |
| #define M41T93_REG_AL1_MONTH 0xa |
| #define M41T93_REG_AL1_DATE 0xb |
| #define M41T93_REG_AL1_HOUR 0xc |
| #define M41T93_REG_AL1_MIN 0xd |
| #define M41T93_REG_AL1_SEC 0xe |
| #define M41T93_BIT_A1IE BIT(7) |
| #define M41T93_BIT_ABE BIT(5) |
| #define M41T93_FLAG_AF1 BIT(6) |
| #define M41T93_SRAM_BASE 0x19 |
| #define M41T93_REG_SQW 0x13 |
| #define M41T93_SQW_RS_MASK 0xf0 |
| #define M41T93_SQW_RS_SHIFT 4 |
| #define M41T93_BIT_SQWE BIT(6) |
| #define M41T93_REG_WATCHDOG 0x9 |
| #define M41T93_WDT_RB_MASK 0x3 |
| #define M41T93_WDT_BMB_MASK 0x7c |
| #define M41T93_WDT_BMB_SHIFT 2 |
| |
| #define M41T93_REG_ALM_HOUR_HT 0xc |
| #define M41T93_REG_FLAGS 0xf |
| |
| #define M41T93_FLAG_ST (1 << 7) |
| #define M41T93_FLAG_OF (1 << 2) |
| #define M41T93_FLAG_BL (1 << 4) |
| #define M41T93_FLAG_HT (1 << 6) |
| |
| struct m41t93_data { |
| struct rtc_device *rtc; |
| struct regmap *regmap; |
| #ifdef CONFIG_COMMON_CLK |
| struct clk_hw clks; |
| #endif |
| #ifdef CONFIG_WATCHDOG |
| struct watchdog_device wdd; |
| #endif |
| }; |
| |
| static int m41t93_set_time(struct device *dev, struct rtc_time *tm) |
| { |
| struct m41t93_data *m41t93 = dev_get_drvdata(dev); |
| int tmp, ret; |
| u8 buf[8] = {0}; /* 8 data bytes */ |
| u8 * const data = &buf[0]; /* ptr to first data byte */ |
| |
| dev_dbg(dev, "%s secs=%d, mins=%d, " |
| "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", |
| "write", tm->tm_sec, tm->tm_min, |
| tm->tm_hour, tm->tm_mday, |
| tm->tm_mon, tm->tm_year, tm->tm_wday); |
| |
| if (tm->tm_year < 100) { |
| dev_warn(dev, "unsupported date (before 2000-01-01).\n"); |
| return -EINVAL; |
| } |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_FLAGS, &tmp); |
| if (ret < 0) |
| return ret; |
| |
| if (tmp & M41T93_FLAG_OF) { |
| dev_warn(dev, "OF bit is set, resetting.\n"); |
| regmap_write(m41t93->regmap, M41T93_REG_FLAGS, tmp & ~M41T93_FLAG_OF); |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_FLAGS, &tmp); |
| if (ret < 0) { |
| return ret; |
| } else if (tmp & M41T93_FLAG_OF) { |
| /* OF cannot be immediately reset: oscillator has to be |
| * restarted. */ |
| u8 reset_osc = buf[M41T93_REG_ST_SEC] | M41T93_FLAG_ST; |
| |
| dev_warn(dev, |
| "OF bit is still set, kickstarting clock.\n"); |
| regmap_write(m41t93->regmap, M41T93_REG_ST_SEC, reset_osc); |
| reset_osc &= ~M41T93_FLAG_ST; |
| regmap_write(m41t93->regmap, M41T93_REG_ST_SEC, reset_osc); |
| } |
| } |
| |
| data[M41T93_REG_SSEC] = 0; |
| data[M41T93_REG_ST_SEC] = bin2bcd(tm->tm_sec); |
| data[M41T93_REG_MIN] = bin2bcd(tm->tm_min); |
| data[M41T93_REG_CENT_HOUR] = bin2bcd(tm->tm_hour) | |
| ((tm->tm_year/100-1) << 6); |
| data[M41T93_REG_DAY] = bin2bcd(tm->tm_mday); |
| data[M41T93_REG_WDAY] = bin2bcd(tm->tm_wday + 1); |
| data[M41T93_REG_MON] = bin2bcd(tm->tm_mon + 1); |
| data[M41T93_REG_YEAR] = bin2bcd(tm->tm_year % 100); |
| |
| return regmap_bulk_write(m41t93->regmap, M41T93_REG_SSEC, buf, sizeof(buf)); |
| } |
| |
| |
| static int m41t93_get_time(struct device *dev, struct rtc_time *tm) |
| { |
| struct m41t93_data *m41t93 = dev_get_drvdata(dev); |
| u8 buf[8]; |
| int century_after_1900; |
| int tmp; |
| int ret = 0; |
| |
| /* Check status of clock. Two states must be considered: |
| 1. halt bit (HT) is set: the clock is running but update of readout |
| registers has been disabled due to power failure. This is normal |
| case after poweron. Time is valid after resetting HT bit. |
| 2. oscillator fail bit (OF) is set: time is invalid. |
| */ |
| ret = regmap_read(m41t93->regmap, M41T93_REG_ALM_HOUR_HT, &tmp); |
| if (ret < 0) |
| return ret; |
| |
| if (tmp & M41T93_FLAG_HT) { |
| dev_dbg(dev, "HT bit is set, reenable clock update.\n"); |
| regmap_write(m41t93->regmap, M41T93_REG_ALM_HOUR_HT, |
| tmp & ~M41T93_FLAG_HT); |
| } |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_FLAGS, &tmp); |
| if (ret < 0) |
| return ret; |
| |
| if (tmp & M41T93_FLAG_OF) { |
| ret = -EINVAL; |
| dev_warn(dev, "OF bit is set, write time to restart.\n"); |
| } |
| |
| if (tmp & M41T93_FLAG_BL) |
| dev_warn(dev, "BL bit is set, replace battery.\n"); |
| |
| /* read actual time/date */ |
| ret = regmap_bulk_read(m41t93->regmap, M41T93_REG_SSEC, buf, sizeof(buf)); |
| if (ret < 0) |
| return ret; |
| |
| tm->tm_sec = bcd2bin(buf[M41T93_REG_ST_SEC]); |
| tm->tm_min = bcd2bin(buf[M41T93_REG_MIN]); |
| tm->tm_hour = bcd2bin(buf[M41T93_REG_CENT_HOUR] & 0x3f); |
| tm->tm_mday = bcd2bin(buf[M41T93_REG_DAY]); |
| tm->tm_mon = bcd2bin(buf[M41T93_REG_MON]) - 1; |
| tm->tm_wday = bcd2bin(buf[M41T93_REG_WDAY] & 0x0f) - 1; |
| |
| century_after_1900 = (buf[M41T93_REG_CENT_HOUR] >> 6) + 1; |
| tm->tm_year = bcd2bin(buf[M41T93_REG_YEAR]) + century_after_1900 * 100; |
| |
| dev_dbg(dev, "%s secs=%d, mins=%d, " |
| "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", |
| "read", tm->tm_sec, tm->tm_min, |
| tm->tm_hour, tm->tm_mday, |
| tm->tm_mon, tm->tm_year, tm->tm_wday); |
| |
| return ret; |
| } |
| |
| static int m41t93_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) |
| { |
| struct m41t93_data *m41t93 = dev_get_drvdata(dev); |
| int ret; |
| unsigned int val; |
| u8 alarm_vals[5] = {0}; |
| |
| ret = regmap_bulk_write(m41t93->regmap, M41T93_REG_AL1_DATE, alarm_vals, 4); |
| if (ret) |
| return ret; |
| |
| /* Set alarm values */ |
| alarm_vals[0] = bin2bcd(alrm->time.tm_mon + 1) & 0x1f; |
| alarm_vals[1] = bin2bcd(alrm->time.tm_mday) & 0x3f; |
| alarm_vals[2] = bin2bcd(alrm->time.tm_hour) & 0x3f; |
| alarm_vals[3] = bin2bcd(alrm->time.tm_min) & 0x7f; |
| alarm_vals[4] = bin2bcd(alrm->time.tm_sec) & 0x7f; |
| |
| if (alrm->enabled) { |
| /* Enable alarm IRQ generation */ |
| alarm_vals[0] |= M41T93_BIT_A1IE | M41T93_BIT_ABE; |
| } |
| |
| /* Preserve SQWE bit */ |
| ret = regmap_read(m41t93->regmap, M41T93_REG_AL1_MONTH, &val); |
| if (ret) |
| return ret; |
| |
| alarm_vals[0] |= val & 0x40; |
| |
| ret = regmap_bulk_write(m41t93->regmap, M41T93_REG_AL1_MONTH, |
| alarm_vals, sizeof(alarm_vals)); |
| if (ret) |
| return ret; |
| |
| /* Device address pointer is now at FLAG register, move it to other location |
| * to finish setting alarm, as recommended by the datasheet. |
| * We do read of AL1_MONTH register to achieve this. |
| */ |
| ret = regmap_read(m41t93->regmap, M41T93_REG_AL1_MONTH, &val); |
| if (ret) |
| return ret; |
| |
| if (bcd2bin(val & 0x1f) == (alrm->time.tm_mon & 0x1f)) |
| dev_notice(dev, "Alarm set successfully\n"); |
| |
| return 0; |
| } |
| |
| static int m41t93_get_alarm(struct device *dev, struct rtc_wkalrm *alrm) |
| { |
| struct m41t93_data *m41t93 = dev_get_drvdata(dev); |
| int ret; |
| unsigned int val; |
| u8 alarm_vals[5] = {0}; |
| |
| ret = regmap_bulk_read(m41t93->regmap, M41T93_REG_AL1_MONTH, |
| alarm_vals, sizeof(alarm_vals)); |
| if (ret) |
| return ret; |
| |
| alrm->time.tm_mon = bcd2bin(alarm_vals[0] & 0x1f) - 1; |
| alrm->time.tm_mday = bcd2bin(alarm_vals[1] & 0x3f); |
| alrm->time.tm_hour = bcd2bin(alarm_vals[2] & 0x3f); |
| alrm->time.tm_min = bcd2bin(alarm_vals[3] & 0x7f); |
| alrm->time.tm_sec = bcd2bin(alarm_vals[4] & 0x7f); |
| |
| alrm->enabled = !!(alarm_vals[0] & M41T93_BIT_A1IE); |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_FLAGS, &val); |
| if (ret) |
| return ret; |
| |
| alrm->pending = (val & M41T93_FLAG_AF1) && alrm->enabled; |
| |
| return 0; |
| } |
| |
| static int m41t93_alarm_irq_enable(struct device *dev, unsigned int enabled) |
| { |
| struct m41t93_data *m41t93 = dev_get_drvdata(dev); |
| unsigned int val; |
| int ret; |
| |
| val = enabled ? M41T93_BIT_A1IE | M41T93_BIT_ABE : 0; |
| |
| ret = regmap_update_bits(m41t93->regmap, M41T93_REG_AL1_MONTH, |
| M41T93_BIT_A1IE | M41T93_BIT_ABE, val); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| |
| static const struct rtc_class_ops m41t93_rtc_ops = { |
| .read_time = m41t93_get_time, |
| .set_time = m41t93_set_time, |
| .set_alarm = m41t93_set_alarm, |
| .read_alarm = m41t93_get_alarm, |
| .alarm_irq_enable = m41t93_alarm_irq_enable, |
| }; |
| |
| #ifdef CONFIG_COMMON_CLK |
| #define clk_sqw_to_m41t93_data(clk) \ |
| container_of(clk, struct m41t93_data, clks) |
| |
| /* m41t93 RTC clock output support */ |
| static unsigned long m41t93_clk_rates[] = { |
| 0, |
| 32768, /* RS3:RS0 = 0b0001 */ |
| 8192, |
| 4096, |
| 2048, |
| 1024, |
| 512, |
| 256, |
| 128, |
| 64, |
| 32, |
| 16, |
| 8, |
| 4, |
| 2, |
| 1, /* RS3:RS0 = 0b1111 */ |
| }; |
| |
| static unsigned long m41t93_clk_sqw_recalc_rate(struct clk_hw *hw, |
| unsigned long parent_rate) |
| { |
| int ret; |
| unsigned int rate_id; |
| struct m41t93_data *m41t93 = clk_sqw_to_m41t93_data(hw); |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_SQW, &rate_id); |
| if (ret) |
| return ret; |
| |
| rate_id &= M41T93_SQW_RS_MASK; |
| rate_id >>= M41T93_SQW_RS_SHIFT; |
| |
| return m41t93_clk_rates[rate_id]; |
| } |
| |
| static int m41t93_clk_sqw_determine_rate(struct clk_hw *hw, |
| struct clk_rate_request *req) |
| { |
| int i; |
| |
| for (i = 1; i < ARRAY_SIZE(m41t93_clk_rates); i++) { |
| if (req->rate >= m41t93_clk_rates[i]) { |
| req->rate = m41t93_clk_rates[i]; |
| return 0; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int m41t93_clk_sqw_set_rate(struct clk_hw *hw, unsigned long rate, |
| unsigned long parent_rate) |
| { |
| int id, ret; |
| struct m41t93_data *m41t93 = clk_sqw_to_m41t93_data(hw); |
| |
| for (id = 0; id < ARRAY_SIZE(m41t93_clk_rates); id++) { |
| if (m41t93_clk_rates[id] == rate) |
| break; |
| } |
| |
| if (id >= ARRAY_SIZE(m41t93_clk_rates)) |
| return -EINVAL; |
| |
| ret = regmap_update_bits(m41t93->regmap, M41T93_REG_SQW, |
| M41T93_SQW_RS_MASK, id << M41T93_SQW_RS_SHIFT); |
| |
| return ret; |
| } |
| |
| static int m41t93_clk_sqw_prepare(struct clk_hw *hw) |
| { |
| int ret; |
| struct m41t93_data *m41t93 = clk_sqw_to_m41t93_data(hw); |
| |
| ret = regmap_update_bits(m41t93->regmap, M41T93_REG_AL1_MONTH, |
| M41T93_BIT_SQWE, M41T93_BIT_SQWE); |
| |
| return ret; |
| } |
| |
| static void m41t93_clk_sqw_unprepare(struct clk_hw *hw) |
| { |
| struct m41t93_data *m41t93 = clk_sqw_to_m41t93_data(hw); |
| |
| regmap_update_bits(m41t93->regmap, M41T93_REG_AL1_MONTH, |
| M41T93_BIT_SQWE, 0); |
| } |
| |
| static int m41t93_clk_sqw_is_prepared(struct clk_hw *hw) |
| { |
| int ret; |
| struct m41t93_data *m41t93 = clk_sqw_to_m41t93_data(hw); |
| unsigned int status; |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_AL1_MONTH, &status); |
| if (ret) |
| return ret; |
| |
| return !!(status & M41T93_BIT_SQWE); |
| } |
| |
| static const struct clk_ops m41t93_clk_sqw_ops = { |
| .prepare = m41t93_clk_sqw_prepare, |
| .unprepare = m41t93_clk_sqw_unprepare, |
| .is_prepared = m41t93_clk_sqw_is_prepared, |
| .recalc_rate = m41t93_clk_sqw_recalc_rate, |
| .set_rate = m41t93_clk_sqw_set_rate, |
| .determine_rate = m41t93_clk_sqw_determine_rate, |
| }; |
| |
| static int rtc_m41t93_clks_register(struct device *dev, struct m41t93_data *m41t93) |
| { |
| struct device_node *node = dev->of_node; |
| struct clk *clk; |
| struct clk_init_data init = {0}; |
| |
| init.name = "m41t93_clk_sqw"; |
| init.ops = &m41t93_clk_sqw_ops; |
| |
| m41t93->clks.init = &init; |
| |
| /* Register the clock with CCF */ |
| clk = devm_clk_register(dev, &m41t93->clks); |
| if (IS_ERR(clk)) |
| return PTR_ERR(clk); |
| |
| if (node) |
| of_clk_add_provider(node, of_clk_src_simple_get, clk); |
| |
| return 0; |
| } |
| #endif |
| |
| #ifdef CONFIG_WATCHDOG |
| static int m41t93_wdt_ping(struct watchdog_device *wdd) |
| { |
| u8 resolution, mult; |
| u8 val = 0; |
| int ret; |
| struct m41t93_data *m41t93 = watchdog_get_drvdata(wdd); |
| |
| /* Resolution supported by hardware |
| * 0b00 : 1/16 seconds |
| * 0b01 : 1/4 second |
| * 0b10 : 1 second |
| * 0b11 : 4 seconds |
| */ |
| resolution = 0x2; /* hardcode resolution to 1s */ |
| mult = wdd->timeout; |
| val = resolution | (mult << M41T93_WDT_BMB_SHIFT & M41T93_WDT_BMB_MASK); |
| |
| ret = regmap_write_bits(m41t93->regmap, M41T93_REG_WATCHDOG, |
| M41T93_WDT_RB_MASK | M41T93_WDT_BMB_MASK, val); |
| |
| return ret; |
| } |
| |
| static int m41t93_wdt_start(struct watchdog_device *wdd) |
| { |
| return m41t93_wdt_ping(wdd); |
| } |
| |
| static int m41t93_wdt_stop(struct watchdog_device *wdd) |
| { |
| struct m41t93_data *m41t93 = watchdog_get_drvdata(wdd); |
| |
| /* Write 0 to watchdog register */ |
| return regmap_write_bits(m41t93->regmap, M41T93_REG_WATCHDOG, |
| M41T93_WDT_RB_MASK | M41T93_WDT_BMB_MASK, 0); |
| } |
| |
| static int m41t93_wdt_set_timeout(struct watchdog_device *wdd, |
| unsigned int new_timeout) |
| { |
| wdd->timeout = new_timeout; |
| |
| return 0; |
| } |
| |
| static const struct watchdog_info m41t93_wdt_info = { |
| .identity = "m41t93 rtc Watchdog", |
| .options = WDIOF_ALARMONLY | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, |
| }; |
| |
| static const struct watchdog_ops m41t93_watchdog_ops = { |
| .owner = THIS_MODULE, |
| .start = m41t93_wdt_start, |
| .stop = m41t93_wdt_stop, |
| .ping = m41t93_wdt_ping, |
| .set_timeout = m41t93_wdt_set_timeout, |
| }; |
| |
| static int m41t93_watchdog_register(struct device *dev, struct m41t93_data *m41t93) |
| { |
| int ret; |
| |
| m41t93->wdd.parent = dev; |
| m41t93->wdd.info = &m41t93_wdt_info; |
| m41t93->wdd.ops = &m41t93_watchdog_ops; |
| m41t93->wdd.min_timeout = 0; |
| m41t93->wdd.max_timeout = 10; |
| m41t93->wdd.timeout = 3; /* Default timeout is 3 sec */ |
| m41t93->wdd.status = WATCHDOG_NOWAYOUT_INIT_STATUS; |
| |
| watchdog_set_drvdata(&m41t93->wdd, m41t93); |
| |
| ret = devm_watchdog_register_device(dev, &m41t93->wdd); |
| if (ret) { |
| dev_warn(dev, "Failed to register watchdog\n"); |
| return ret; |
| } |
| |
| /* Disable watchdog at start */ |
| ret = m41t93_wdt_stop(&m41t93->wdd); |
| |
| return ret; |
| } |
| #endif |
| |
| static struct spi_driver m41t93_driver; |
| |
| static const struct regmap_config regmap_config = { |
| .reg_bits = 8, |
| .val_bits = 8, |
| .read_flag_mask = 0x00, |
| .write_flag_mask = 0x80, |
| .zero_flag_mask = true, |
| }; |
| |
| static int m41t93_probe(struct spi_device *spi) |
| { |
| int res, ret; |
| struct m41t93_data *m41t93; |
| |
| spi->bits_per_word = 8; |
| spi_setup(spi); |
| |
| m41t93 = devm_kzalloc(&spi->dev, sizeof(struct m41t93_data), GFP_KERNEL); |
| |
| if (!m41t93) |
| return -ENOMEM; |
| |
| /* Set up regmap to access device registers*/ |
| m41t93->regmap = devm_regmap_init_spi(spi, ®map_config); |
| if (IS_ERR(m41t93->regmap)) { |
| dev_err(&spi->dev, "regmap init failure\n"); |
| return PTR_ERR(m41t93->regmap); |
| } |
| |
| ret = regmap_read(m41t93->regmap, M41T93_REG_WDAY, &res); |
| if (ret < 0) { |
| dev_err(&spi->dev, "IO error\n"); |
| return -EIO; |
| } |
| |
| if (res < 0 || (res & 0xf8) != 0) { |
| dev_err(&spi->dev, "not found 0x%x.\n", res); |
| return -ENODEV; |
| } |
| |
| spi_set_drvdata(spi, m41t93); |
| |
| m41t93->rtc = devm_rtc_device_register(&spi->dev, m41t93_driver.driver.name, |
| &m41t93_rtc_ops, THIS_MODULE); |
| if (IS_ERR(m41t93->rtc)) |
| return PTR_ERR(m41t93->rtc); |
| |
| #ifdef CONFIG_COMMON_CLK |
| ret = rtc_m41t93_clks_register(&spi->dev, m41t93); |
| if (ret) |
| dev_warn(&spi->dev, "Unable to register clock\n"); |
| #endif |
| #ifdef CONFIG_WATCHDOG |
| ret = m41t93_watchdog_register(&spi->dev, m41t93); |
| if (ret) |
| dev_warn(&spi->dev, "Unable to register watchdog\n"); |
| #endif |
| |
| return 0; |
| } |
| |
| static const struct of_device_id m41t93_dt_match[] = { |
| { .compatible = "st,m41t93" }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, m41t93_dt_match); |
| |
| static struct spi_driver m41t93_driver = { |
| .driver = { |
| .name = "rtc-m41t93", |
| .of_match_table = m41t93_dt_match, |
| }, |
| .probe = m41t93_probe, |
| }; |
| |
| module_spi_driver(m41t93_driver); |
| |
| MODULE_AUTHOR("Nikolaus Voss <n.voss@weinmann.de>"); |
| MODULE_DESCRIPTION("Driver for ST M41T93 SPI RTC"); |
| MODULE_LICENSE("GPL"); |
| MODULE_ALIAS("spi:rtc-m41t93"); |