root/drivers/rtc/rtc-rv3028.c
// SPDX-License-Identifier: GPL-2.0
/*
 * RTC driver for the Micro Crystal RV3028
 *
 * Copyright (C) 2019 Micro Crystal SA
 *
 * Alexandre Belloni <alexandre.belloni@bootlin.com>
 *
 */

#include <linux/clk-provider.h>
#include <linux/bcd.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/regmap.h>
#include <linux/rtc.h>

#define RV3028_SEC                      0x00
#define RV3028_MIN                      0x01
#define RV3028_HOUR                     0x02
#define RV3028_WDAY                     0x03
#define RV3028_DAY                      0x04
#define RV3028_MONTH                    0x05
#define RV3028_YEAR                     0x06
#define RV3028_ALARM_MIN                0x07
#define RV3028_ALARM_HOUR               0x08
#define RV3028_ALARM_DAY                0x09
#define RV3028_STATUS                   0x0E
#define RV3028_CTRL1                    0x0F
#define RV3028_CTRL2                    0x10
#define RV3028_EVT_CTRL                 0x13
#define RV3028_TS_COUNT                 0x14
#define RV3028_TS_SEC                   0x15
#define RV3028_RAM1                     0x1F
#define RV3028_EEPROM_ADDR              0x25
#define RV3028_EEPROM_DATA              0x26
#define RV3028_EEPROM_CMD               0x27
#define RV3028_CLKOUT                   0x35
#define RV3028_OFFSET                   0x36
#define RV3028_BACKUP                   0x37

#define RV3028_STATUS_PORF              BIT(0)
#define RV3028_STATUS_EVF               BIT(1)
#define RV3028_STATUS_AF                BIT(2)
#define RV3028_STATUS_TF                BIT(3)
#define RV3028_STATUS_UF                BIT(4)
#define RV3028_STATUS_BSF               BIT(5)
#define RV3028_STATUS_CLKF              BIT(6)
#define RV3028_STATUS_EEBUSY            BIT(7)

#define RV3028_CLKOUT_FD_MASK           GENMASK(2, 0)
#define RV3028_CLKOUT_PORIE             BIT(3)
#define RV3028_CLKOUT_CLKSY             BIT(6)
#define RV3028_CLKOUT_CLKOE             BIT(7)

#define RV3028_CTRL1_EERD               BIT(3)
#define RV3028_CTRL1_WADA               BIT(5)

#define RV3028_CTRL2_RESET              BIT(0)
#define RV3028_CTRL2_12_24              BIT(1)
#define RV3028_CTRL2_EIE                BIT(2)
#define RV3028_CTRL2_AIE                BIT(3)
#define RV3028_CTRL2_TIE                BIT(4)
#define RV3028_CTRL2_UIE                BIT(5)
#define RV3028_CTRL2_TSE                BIT(7)

#define RV3028_EVT_CTRL_TSR             BIT(2)

#define RV3028_EEPROM_CMD_UPDATE        0x11
#define RV3028_EEPROM_CMD_WRITE         0x21
#define RV3028_EEPROM_CMD_READ          0x22

#define RV3028_EEBUSY_POLL              10000
#define RV3028_EEBUSY_TIMEOUT           100000

#define RV3028_BACKUP_TCE               BIT(5)
#define RV3028_BACKUP_TCR_MASK          GENMASK(1,0)
#define RV3028_BACKUP_BSM               GENMASK(3,2)

#define RV3028_BACKUP_BSM_DSM           0x1
#define RV3028_BACKUP_BSM_LSM           0x3

#define OFFSET_STEP_PPT                 953674

enum rv3028_type {
        rv_3028,
};

struct rv3028_data {
        struct regmap *regmap;
        struct rtc_device *rtc;
        enum rv3028_type type;
#ifdef CONFIG_COMMON_CLK
        struct clk_hw clkout_hw;
#endif
};

static u16 rv3028_trickle_resistors[] = {3000, 5000, 9000, 15000};

static ssize_t timestamp0_store(struct device *dev,
                                struct device_attribute *attr,
                                const char *buf, size_t count)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev->parent);

        regmap_update_bits(rv3028->regmap, RV3028_EVT_CTRL, RV3028_EVT_CTRL_TSR,
                           RV3028_EVT_CTRL_TSR);

        return count;
};

static ssize_t timestamp0_show(struct device *dev,
                               struct device_attribute *attr, char *buf)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev->parent);
        struct rtc_time tm;
        unsigned int count;
        u8 date[6];
        int ret;

        ret = regmap_read(rv3028->regmap, RV3028_TS_COUNT, &count);
        if (ret)
                return ret;

        if (!count)
                return 0;

        ret = regmap_bulk_read(rv3028->regmap, RV3028_TS_SEC, date,
                               sizeof(date));
        if (ret)
                return ret;

        tm.tm_sec = bcd2bin(date[0]);
        tm.tm_min = bcd2bin(date[1]);
        tm.tm_hour = bcd2bin(date[2]);
        tm.tm_mday = bcd2bin(date[3]);
        tm.tm_mon = bcd2bin(date[4]) - 1;
        tm.tm_year = bcd2bin(date[5]) + 100;

        ret = rtc_valid_tm(&tm);
        if (ret)
                return ret;

        return sprintf(buf, "%llu\n",
                       (unsigned long long)rtc_tm_to_time64(&tm));
};

static DEVICE_ATTR_RW(timestamp0);

static ssize_t timestamp0_count_show(struct device *dev,
                                     struct device_attribute *attr, char *buf)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev->parent);
        unsigned int count;
        int ret;

        ret = regmap_read(rv3028->regmap, RV3028_TS_COUNT, &count);
        if (ret)
                return ret;

        return sprintf(buf, "%u\n", count);
};

static DEVICE_ATTR_RO(timestamp0_count);

static struct attribute *rv3028_attrs[] = {
        &dev_attr_timestamp0.attr,
        &dev_attr_timestamp0_count.attr,
        NULL
};

static const struct attribute_group rv3028_attr_group = {
        .attrs  = rv3028_attrs,
};

static int rv3028_exit_eerd(struct rv3028_data *rv3028, u32 eerd)
{
        if (eerd)
                return 0;

        return regmap_update_bits(rv3028->regmap, RV3028_CTRL1, RV3028_CTRL1_EERD, 0);
}

static int rv3028_enter_eerd(struct rv3028_data *rv3028, u32 *eerd)
{
        u32 ctrl1, status;
        int ret;

        ret = regmap_read(rv3028->regmap, RV3028_CTRL1, &ctrl1);
        if (ret)
                return ret;

        *eerd = ctrl1 & RV3028_CTRL1_EERD;
        if (*eerd)
                return 0;

        ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL1,
                                 RV3028_CTRL1_EERD, RV3028_CTRL1_EERD);
        if (ret)
                return ret;

        ret = regmap_read_poll_timeout(rv3028->regmap, RV3028_STATUS, status,
                                       !(status & RV3028_STATUS_EEBUSY),
                                       RV3028_EEBUSY_POLL, RV3028_EEBUSY_TIMEOUT);
        if (ret) {
                rv3028_exit_eerd(rv3028, *eerd);

                return ret;
        }

        return 0;
}

static int rv3028_update_eeprom(struct rv3028_data *rv3028, u32 eerd)
{
        u32 status;
        int ret;

        ret = regmap_write(rv3028->regmap, RV3028_EEPROM_CMD, 0x0);
        if (ret)
                goto exit_eerd;

        ret = regmap_write(rv3028->regmap, RV3028_EEPROM_CMD, RV3028_EEPROM_CMD_UPDATE);
        if (ret)
                goto exit_eerd;

        usleep_range(63000, RV3028_EEBUSY_TIMEOUT);

        ret = regmap_read_poll_timeout(rv3028->regmap, RV3028_STATUS, status,
                                       !(status & RV3028_STATUS_EEBUSY),
                                       RV3028_EEBUSY_POLL, RV3028_EEBUSY_TIMEOUT);

exit_eerd:
        rv3028_exit_eerd(rv3028, eerd);

        return ret;
}

static int rv3028_update_cfg(struct rv3028_data *rv3028, unsigned int reg,
                             unsigned int mask, unsigned int val)
{
        u32 eerd;
        int ret;

        ret = rv3028_enter_eerd(rv3028, &eerd);
        if (ret)
                return ret;

        ret = regmap_update_bits(rv3028->regmap, reg, mask, val);
        if (ret) {
                rv3028_exit_eerd(rv3028, eerd);
                return ret;
        }

        return rv3028_update_eeprom(rv3028, eerd);
}

static irqreturn_t rv3028_handle_irq(int irq, void *dev_id)
{
        struct rv3028_data *rv3028 = dev_id;
        unsigned long events = 0;
        u32 status = 0, ctrl = 0;

        if (regmap_read(rv3028->regmap, RV3028_STATUS, &status) < 0 ||
           status == 0) {
                return IRQ_NONE;
        }

        status &= ~RV3028_STATUS_PORF;

        if (status & RV3028_STATUS_TF) {
                status |= RV3028_STATUS_TF;
                ctrl |= RV3028_CTRL2_TIE;
                events |= RTC_PF;
        }

        if (status & RV3028_STATUS_AF) {
                status |= RV3028_STATUS_AF;
                ctrl |= RV3028_CTRL2_AIE;
                events |= RTC_AF;
        }

        if (status & RV3028_STATUS_UF) {
                status |= RV3028_STATUS_UF;
                ctrl |= RV3028_CTRL2_UIE;
                events |= RTC_UF;
        }

        if (events) {
                rtc_update_irq(rv3028->rtc, 1, events);
                regmap_update_bits(rv3028->regmap, RV3028_STATUS, status, 0);
                regmap_update_bits(rv3028->regmap, RV3028_CTRL2, ctrl, 0);
        }

        if (status & RV3028_STATUS_EVF) {
                sysfs_notify(&rv3028->rtc->dev.kobj, NULL,
                             dev_attr_timestamp0.attr.name);
                dev_warn(&rv3028->rtc->dev, "event detected");
        }

        return IRQ_HANDLED;
}

static int rv3028_get_time(struct device *dev, struct rtc_time *tm)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        u8 date[7];
        int ret, status;

        ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status);
        if (ret < 0)
                return ret;

        if (status & RV3028_STATUS_PORF)
                return -EINVAL;

        ret = regmap_bulk_read(rv3028->regmap, RV3028_SEC, date, sizeof(date));
        if (ret)
                return ret;

        tm->tm_sec  = bcd2bin(date[RV3028_SEC] & 0x7f);
        tm->tm_min  = bcd2bin(date[RV3028_MIN] & 0x7f);
        tm->tm_hour = bcd2bin(date[RV3028_HOUR] & 0x3f);
        tm->tm_wday = date[RV3028_WDAY] & 0x7f;
        tm->tm_mday = bcd2bin(date[RV3028_DAY] & 0x3f);
        tm->tm_mon  = bcd2bin(date[RV3028_MONTH] & 0x1f) - 1;
        tm->tm_year = bcd2bin(date[RV3028_YEAR]) + 100;

        return 0;
}

static int rv3028_set_time(struct device *dev, struct rtc_time *tm)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        u8 date[7];
        int ret;

        date[RV3028_SEC]   = bin2bcd(tm->tm_sec);
        date[RV3028_MIN]   = bin2bcd(tm->tm_min);
        date[RV3028_HOUR]  = bin2bcd(tm->tm_hour);
        date[RV3028_WDAY]  = tm->tm_wday;
        date[RV3028_DAY]   = bin2bcd(tm->tm_mday);
        date[RV3028_MONTH] = bin2bcd(tm->tm_mon + 1);
        date[RV3028_YEAR]  = bin2bcd(tm->tm_year - 100);

        /*
         * Writing to the Seconds register has the same effect as setting RESET
         * bit to 1
         */
        ret = regmap_bulk_write(rv3028->regmap, RV3028_SEC, date,
                                sizeof(date));
        if (ret)
                return ret;

        ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS,
                                 RV3028_STATUS_PORF, 0);

        return ret;
}

static int rv3028_get_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        u8 alarmvals[3];
        int status, ctrl, ret;

        ret = regmap_bulk_read(rv3028->regmap, RV3028_ALARM_MIN, alarmvals,
                               sizeof(alarmvals));
        if (ret)
                return ret;

        ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status);
        if (ret < 0)
                return ret;

        ret = regmap_read(rv3028->regmap, RV3028_CTRL2, &ctrl);
        if (ret < 0)
                return ret;

        alrm->time.tm_sec  = 0;
        alrm->time.tm_min  = bcd2bin(alarmvals[0] & 0x7f);
        alrm->time.tm_hour = bcd2bin(alarmvals[1] & 0x3f);
        alrm->time.tm_mday = bcd2bin(alarmvals[2] & 0x3f);

        alrm->enabled = !!(ctrl & RV3028_CTRL2_AIE);
        alrm->pending = (status & RV3028_STATUS_AF) && alrm->enabled;

        return 0;
}

static int rv3028_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        u8 alarmvals[3];
        u8 ctrl = 0;
        int ret;

        /* The alarm has no seconds, round up to nearest minute */
        if (alrm->time.tm_sec) {
                time64_t alarm_time = rtc_tm_to_time64(&alrm->time);

                alarm_time += 60 - alrm->time.tm_sec;
                rtc_time64_to_tm(alarm_time, &alrm->time);
        }

        ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2,
                                 RV3028_CTRL2_AIE | RV3028_CTRL2_UIE, 0);
        if (ret)
                return ret;

        alarmvals[0] = bin2bcd(alrm->time.tm_min);
        alarmvals[1] = bin2bcd(alrm->time.tm_hour);
        alarmvals[2] = bin2bcd(alrm->time.tm_mday);

        ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS,
                                 RV3028_STATUS_AF, 0);
        if (ret)
                return ret;

        ret = regmap_bulk_write(rv3028->regmap, RV3028_ALARM_MIN, alarmvals,
                                sizeof(alarmvals));
        if (ret)
                return ret;

        if (alrm->enabled) {
                if (rv3028->rtc->uie_rtctimer.enabled)
                        ctrl |= RV3028_CTRL2_UIE;
                if (rv3028->rtc->aie_timer.enabled)
                        ctrl |= RV3028_CTRL2_AIE;
        }

        ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2,
                                 RV3028_CTRL2_UIE | RV3028_CTRL2_AIE, ctrl);

        return ret;
}

static int rv3028_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        int ctrl = 0, ret;

        if (enabled) {
                if (rv3028->rtc->uie_rtctimer.enabled)
                        ctrl |= RV3028_CTRL2_UIE;
                if (rv3028->rtc->aie_timer.enabled)
                        ctrl |= RV3028_CTRL2_AIE;
        }

        ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS,
                                 RV3028_STATUS_AF | RV3028_STATUS_UF, 0);
        if (ret)
                return ret;

        ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2,
                                 RV3028_CTRL2_UIE | RV3028_CTRL2_AIE, ctrl);
        if (ret)
                return ret;

        return 0;
}

static int rv3028_read_offset(struct device *dev, long *offset)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        int ret, value, steps;

        ret = regmap_read(rv3028->regmap, RV3028_OFFSET, &value);
        if (ret < 0)
                return ret;

        steps = sign_extend32(value << 1, 8);

        ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &value);
        if (ret < 0)
                return ret;

        steps += value >> 7;

        *offset = DIV_ROUND_CLOSEST(steps * OFFSET_STEP_PPT, 1000);

        return 0;
}

static int rv3028_set_offset(struct device *dev, long offset)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        u32 eerd;
        int ret;

        offset = clamp(offset, -244141L, 243187L) * 1000;
        offset = DIV_ROUND_CLOSEST(offset, OFFSET_STEP_PPT);

        ret = rv3028_enter_eerd(rv3028, &eerd);
        if (ret)
                return ret;

        ret = regmap_write(rv3028->regmap, RV3028_OFFSET, offset >> 1);
        if (ret < 0)
                goto exit_eerd;

        ret = regmap_update_bits(rv3028->regmap, RV3028_BACKUP, BIT(7),
                                 offset << 7);
        if (ret < 0)
                goto exit_eerd;

        return rv3028_update_eeprom(rv3028, eerd);

exit_eerd:
        rv3028_exit_eerd(rv3028, eerd);

        return ret;

}

static int rv3028_param_get(struct device *dev, struct rtc_param *param)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        int ret;
        u32 value;

        switch(param->param) {
        case RTC_PARAM_BACKUP_SWITCH_MODE:
                ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &value);
                if (ret < 0)
                        return ret;

                value = FIELD_GET(RV3028_BACKUP_BSM, value);

                switch(value) {
                case RV3028_BACKUP_BSM_DSM:
                        param->uvalue = RTC_BSM_DIRECT;
                        break;
                case RV3028_BACKUP_BSM_LSM:
                        param->uvalue = RTC_BSM_LEVEL;
                        break;
                default:
                        param->uvalue = RTC_BSM_DISABLED;
                }
                break;

        default:
                return -EINVAL;
        }

        return 0;
}

static int rv3028_param_set(struct device *dev, struct rtc_param *param)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        u8 mode;

        switch(param->param) {
        case RTC_PARAM_BACKUP_SWITCH_MODE:
                switch (param->uvalue) {
                case RTC_BSM_DISABLED:
                        mode = 0;
                        break;
                case RTC_BSM_DIRECT:
                        mode = RV3028_BACKUP_BSM_DSM;
                        break;
                case RTC_BSM_LEVEL:
                        mode = RV3028_BACKUP_BSM_LSM;
                        break;
                default:
                        return -EINVAL;
                }

                return rv3028_update_cfg(rv3028, RV3028_BACKUP, RV3028_BACKUP_BSM,
                                         FIELD_PREP(RV3028_BACKUP_BSM, mode));

        default:
                return -EINVAL;
        }

        return 0;
}

static int rv3028_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
{
        struct rv3028_data *rv3028 = dev_get_drvdata(dev);
        int status, ret = 0;

        switch (cmd) {
        case RTC_VL_READ:
                ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status);
                if (ret < 0)
                        return ret;

                status = status & RV3028_STATUS_PORF ? RTC_VL_DATA_INVALID : 0;
                return put_user(status, (unsigned int __user *)arg);

        default:
                return -ENOIOCTLCMD;
        }
}

static int rv3028_nvram_write(void *priv, unsigned int offset, void *val,
                              size_t bytes)
{
        return regmap_bulk_write(priv, RV3028_RAM1 + offset, val, bytes);
}

static int rv3028_nvram_read(void *priv, unsigned int offset, void *val,
                             size_t bytes)
{
        return regmap_bulk_read(priv, RV3028_RAM1 + offset, val, bytes);
}

static int rv3028_eeprom_write(void *priv, unsigned int offset, void *val,
                               size_t bytes)
{
        struct rv3028_data *rv3028 = priv;
        u32 status, eerd;
        int i, ret;
        u8 *buf = val;

        ret = rv3028_enter_eerd(rv3028, &eerd);
        if (ret)
                return ret;

        for (i = 0; i < bytes; i++) {
                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_ADDR, offset + i);
                if (ret)
                        goto restore_eerd;

                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_DATA, buf[i]);
                if (ret)
                        goto restore_eerd;

                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_CMD, 0x0);
                if (ret)
                        goto restore_eerd;

                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_CMD,
                                   RV3028_EEPROM_CMD_WRITE);
                if (ret)
                        goto restore_eerd;

                usleep_range(RV3028_EEBUSY_POLL, RV3028_EEBUSY_TIMEOUT);

                ret = regmap_read_poll_timeout(rv3028->regmap, RV3028_STATUS, status,
                                               !(status & RV3028_STATUS_EEBUSY),
                                               RV3028_EEBUSY_POLL,
                                               RV3028_EEBUSY_TIMEOUT);
                if (ret)
                        goto restore_eerd;
        }

restore_eerd:
        rv3028_exit_eerd(rv3028, eerd);

        return ret;
}

static int rv3028_eeprom_read(void *priv, unsigned int offset, void *val,
                              size_t bytes)
{
        struct rv3028_data *rv3028 = priv;
        u32 status, eerd, data;
        int i, ret;
        u8 *buf = val;

        ret = rv3028_enter_eerd(rv3028, &eerd);
        if (ret)
                return ret;

        for (i = 0; i < bytes; i++) {
                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_ADDR, offset + i);
                if (ret)
                        goto restore_eerd;

                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_CMD, 0x0);
                if (ret)
                        goto restore_eerd;

                ret = regmap_write(rv3028->regmap, RV3028_EEPROM_CMD,
                                   RV3028_EEPROM_CMD_READ);
                if (ret)
                        goto restore_eerd;

                ret = regmap_read_poll_timeout(rv3028->regmap, RV3028_STATUS, status,
                                               !(status & RV3028_STATUS_EEBUSY),
                                               RV3028_EEBUSY_POLL,
                                               RV3028_EEBUSY_TIMEOUT);
                if (ret)
                        goto restore_eerd;

                ret = regmap_read(rv3028->regmap, RV3028_EEPROM_DATA, &data);
                if (ret)
                        goto restore_eerd;
                buf[i] = data;
        }

restore_eerd:
        rv3028_exit_eerd(rv3028, eerd);

        return ret;
}

#ifdef CONFIG_COMMON_CLK
#define clkout_hw_to_rv3028(hw) container_of(hw, struct rv3028_data, clkout_hw)

static int clkout_rates[] = {
        32768,
        8192,
        1024,
        64,
        32,
        1,
};

static unsigned long rv3028_clkout_recalc_rate(struct clk_hw *hw,
                                               unsigned long parent_rate)
{
        int clkout, ret;
        struct rv3028_data *rv3028 = clkout_hw_to_rv3028(hw);

        ret = regmap_read(rv3028->regmap, RV3028_CLKOUT, &clkout);
        if (ret < 0)
                return 0;

        clkout &= RV3028_CLKOUT_FD_MASK;
        return clkout_rates[clkout];
}

static int rv3028_clkout_determine_rate(struct clk_hw *hw,
                                        struct clk_rate_request *req)
{
        int i;

        for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
                if (clkout_rates[i] <= req->rate) {
                        req->rate = clkout_rates[i];

                        return 0;
                }

        req->rate = clkout_rates[0];

        return 0;
}

static int rv3028_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
                                  unsigned long parent_rate)
{
        int i, ret;
        u32 enabled;
        struct rv3028_data *rv3028 = clkout_hw_to_rv3028(hw);

        ret = regmap_read(rv3028->regmap, RV3028_CLKOUT, &enabled);
        if (ret < 0)
                return ret;

        ret = regmap_write(rv3028->regmap, RV3028_CLKOUT, 0x0);
        if (ret < 0)
                return ret;

        enabled &= RV3028_CLKOUT_CLKOE;

        for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
                if (clkout_rates[i] == rate)
                        return rv3028_update_cfg(rv3028, RV3028_CLKOUT, 0xff,
                                                 RV3028_CLKOUT_CLKSY | enabled | i);

        return -EINVAL;
}

static int rv3028_clkout_prepare(struct clk_hw *hw)
{
        struct rv3028_data *rv3028 = clkout_hw_to_rv3028(hw);

        return regmap_write(rv3028->regmap, RV3028_CLKOUT,
                            RV3028_CLKOUT_CLKSY | RV3028_CLKOUT_CLKOE);
}

static void rv3028_clkout_unprepare(struct clk_hw *hw)
{
        struct rv3028_data *rv3028 = clkout_hw_to_rv3028(hw);

        regmap_write(rv3028->regmap, RV3028_CLKOUT, 0x0);
        regmap_update_bits(rv3028->regmap, RV3028_STATUS,
                           RV3028_STATUS_CLKF, 0);
}

static int rv3028_clkout_is_prepared(struct clk_hw *hw)
{
        int clkout, ret;
        struct rv3028_data *rv3028 = clkout_hw_to_rv3028(hw);

        ret = regmap_read(rv3028->regmap, RV3028_CLKOUT, &clkout);
        if (ret < 0)
                return ret;

        return !!(clkout & RV3028_CLKOUT_CLKOE);
}

static const struct clk_ops rv3028_clkout_ops = {
        .prepare = rv3028_clkout_prepare,
        .unprepare = rv3028_clkout_unprepare,
        .is_prepared = rv3028_clkout_is_prepared,
        .recalc_rate = rv3028_clkout_recalc_rate,
        .determine_rate = rv3028_clkout_determine_rate,
        .set_rate = rv3028_clkout_set_rate,
};

static int rv3028_clkout_register_clk(struct rv3028_data *rv3028,
                                      struct i2c_client *client)
{
        int ret;
        struct clk *clk;
        struct clk_init_data init;
        struct device_node *node = client->dev.of_node;

        ret = regmap_update_bits(rv3028->regmap, RV3028_STATUS,
                                 RV3028_STATUS_CLKF, 0);
        if (ret < 0)
                return ret;

        init.name = "rv3028-clkout";
        init.ops = &rv3028_clkout_ops;
        init.flags = 0;
        init.parent_names = NULL;
        init.num_parents = 0;
        rv3028->clkout_hw.init = &init;

        /* optional override of the clockname */
        of_property_read_string(node, "clock-output-names", &init.name);

        /* register the clock */
        clk = devm_clk_register(&client->dev, &rv3028->clkout_hw);
        if (!IS_ERR(clk))
                of_clk_add_provider(node, of_clk_src_simple_get, clk);

        return 0;
}
#endif

static const struct rtc_class_ops rv3028_rtc_ops = {
        .read_time = rv3028_get_time,
        .set_time = rv3028_set_time,
        .read_alarm = rv3028_get_alarm,
        .set_alarm = rv3028_set_alarm,
        .alarm_irq_enable = rv3028_alarm_irq_enable,
        .read_offset = rv3028_read_offset,
        .set_offset = rv3028_set_offset,
        .ioctl = rv3028_ioctl,
        .param_get = rv3028_param_get,
        .param_set = rv3028_param_set,
};

static const struct regmap_config regmap_config = {
        .reg_bits = 8,
        .val_bits = 8,
        .max_register = 0x37,
};

static u8 rv3028_set_trickle_charger(struct rv3028_data *rv3028,
                                     struct i2c_client *client)
{
        int ret, val_old, val;
        u32 ohms, chargeable;

        ret = regmap_read(rv3028->regmap, RV3028_BACKUP, &val_old);
        if (ret < 0)
                return ret;

        /* mask out only trickle charger bits */
        val_old = val_old & (RV3028_BACKUP_TCE | RV3028_BACKUP_TCR_MASK);
        val = val_old;

        /* setup trickle charger */
        if (!device_property_read_u32(&client->dev, "trickle-resistor-ohms",
                                      &ohms)) {
                int i;

                for (i = 0; i < ARRAY_SIZE(rv3028_trickle_resistors); i++)
                        if (ohms == rv3028_trickle_resistors[i])
                                break;

                if (i < ARRAY_SIZE(rv3028_trickle_resistors)) {
                        /* enable trickle charger and its resistor */
                        val = RV3028_BACKUP_TCE | i;
                } else {
                        dev_warn(&client->dev, "invalid trickle resistor value\n");
                }
        }

        if (!device_property_read_u32(&client->dev, "aux-voltage-chargeable",
                                      &chargeable)) {
                switch (chargeable) {
                case 0:
                        val &= ~RV3028_BACKUP_TCE;
                        break;
                case 1:
                        val |= RV3028_BACKUP_TCE;
                        break;
                default:
                        dev_warn(&client->dev,
                                 "unsupported aux-voltage-chargeable value\n");
                        break;
                }
        }

        /* only update EEPROM if changes are necessary */
        if (val_old != val) {
                ret = rv3028_update_cfg(rv3028, RV3028_BACKUP, RV3028_BACKUP_TCE |
                                                RV3028_BACKUP_TCR_MASK, val);
                if (ret)
                        return ret;
        }

        return ret;
}

static int rv3028_probe(struct i2c_client *client)
{
        struct rv3028_data *rv3028;
        int ret, status;
        struct nvmem_config nvmem_cfg = {
                .name = "rv3028_nvram",
                .word_size = 1,
                .stride = 1,
                .size = 2,
                .type = NVMEM_TYPE_BATTERY_BACKED,
                .reg_read = rv3028_nvram_read,
                .reg_write = rv3028_nvram_write,
        };
        struct nvmem_config eeprom_cfg = {
                .name = "rv3028_eeprom",
                .word_size = 1,
                .stride = 1,
                .size = 43,
                .type = NVMEM_TYPE_EEPROM,
                .reg_read = rv3028_eeprom_read,
                .reg_write = rv3028_eeprom_write,
        };

        rv3028 = devm_kzalloc(&client->dev, sizeof(struct rv3028_data),
                              GFP_KERNEL);
        if (!rv3028)
                return -ENOMEM;

        rv3028->regmap = devm_regmap_init_i2c(client, &regmap_config);
        if (IS_ERR(rv3028->regmap))
                return PTR_ERR(rv3028->regmap);

        i2c_set_clientdata(client, rv3028);

        ret = regmap_read(rv3028->regmap, RV3028_STATUS, &status);
        if (ret < 0)
                return ret;

        if (status & RV3028_STATUS_AF)
                dev_warn(&client->dev, "An alarm may have been missed.\n");

        rv3028->rtc = devm_rtc_allocate_device(&client->dev);
        if (IS_ERR(rv3028->rtc))
                return PTR_ERR(rv3028->rtc);

        if (client->irq > 0) {
                unsigned long flags;

                /*
                 * If flags = 0, devm_request_threaded_irq() will use IRQ flags
                 * obtained from device tree.
                 */
                if (dev_fwnode(&client->dev))
                        flags = 0;
                else
                        flags = IRQF_TRIGGER_LOW;

                ret = devm_request_threaded_irq(&client->dev, client->irq,
                                                NULL, rv3028_handle_irq,
                                                flags | IRQF_ONESHOT,
                                                "rv3028", rv3028);
                if (ret) {
                        dev_warn(&client->dev, "unable to request IRQ, alarms disabled\n");
                        client->irq = 0;
                }
        }
        if (!client->irq)
                clear_bit(RTC_FEATURE_ALARM, rv3028->rtc->features);

        ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL1,
                                 RV3028_CTRL1_WADA, RV3028_CTRL1_WADA);
        if (ret)
                return ret;

        /* setup timestamping */
        ret = regmap_update_bits(rv3028->regmap, RV3028_CTRL2,
                                 RV3028_CTRL2_EIE | RV3028_CTRL2_TSE,
                                 RV3028_CTRL2_EIE | RV3028_CTRL2_TSE);
        if (ret)
                return ret;

        ret = rv3028_set_trickle_charger(rv3028, client);
        if (ret)
                return ret;

        ret = rtc_add_group(rv3028->rtc, &rv3028_attr_group);
        if (ret)
                return ret;

        set_bit(RTC_FEATURE_BACKUP_SWITCH_MODE, rv3028->rtc->features);

        rv3028->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
        rv3028->rtc->range_max = RTC_TIMESTAMP_END_2099;
        rv3028->rtc->ops = &rv3028_rtc_ops;
        ret = devm_rtc_register_device(rv3028->rtc);
        if (ret)
                return ret;

        nvmem_cfg.priv = rv3028->regmap;
        devm_rtc_nvmem_register(rv3028->rtc, &nvmem_cfg);
        eeprom_cfg.priv = rv3028;
        devm_rtc_nvmem_register(rv3028->rtc, &eeprom_cfg);

#ifdef CONFIG_COMMON_CLK
        rv3028_clkout_register_clk(rv3028, client);
#endif
        return 0;
}

static const struct acpi_device_id rv3028_i2c_acpi_match[] = {
        { "MCRY3028" },
        { }
};
MODULE_DEVICE_TABLE(acpi, rv3028_i2c_acpi_match);

static const __maybe_unused struct of_device_id rv3028_of_match[] = {
        { .compatible = "microcrystal,rv3028", },
        { }
};
MODULE_DEVICE_TABLE(of, rv3028_of_match);

static const struct i2c_device_id rv3028_id_table[] = {
        { .name = "rv3028", },
        { }
};
MODULE_DEVICE_TABLE(i2c, rv3028_id_table);

static struct i2c_driver rv3028_driver = {
        .driver = {
                .name = "rtc-rv3028",
                .acpi_match_table = rv3028_i2c_acpi_match,
                .of_match_table = of_match_ptr(rv3028_of_match),
        },
        .id_table       = rv3028_id_table,
        .probe          = rv3028_probe,
};
module_i2c_driver(rv3028_driver);

MODULE_AUTHOR("Alexandre Belloni <alexandre.belloni@bootlin.com>");
MODULE_DESCRIPTION("Micro Crystal RV3028 RTC driver");
MODULE_LICENSE("GPL v2");