/* * Copyright (c) 2006-2022, RT-Thread Development Team * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2022-12-06 GuEe-GUI first version */ #include "rtc_dm.h" #define DBG_TAG "rtc.hym8563" #define DBG_LVL DBG_INFO #include #define HYM8563_CTL1 0x00 #define HYM8563_CTL1_TEST RT_BIT(7) #define HYM8563_CTL1_STOP RT_BIT(5) #define HYM8563_CTL1_TESTC RT_BIT(3) #define HYM8563_CTL2 0x01 #define HYM8563_CTL2_TI_TP RT_BIT(4) #define HYM8563_CTL2_AF RT_BIT(3) #define HYM8563_CTL2_TF RT_BIT(2) #define HYM8563_CTL2_AIE RT_BIT(1) #define HYM8563_CTL2_TIE RT_BIT(0) #define HYM8563_SEC 0x02 #define HYM8563_SEC_VL RT_BIT(7) #define HYM8563_SEC_MASK 0x7f #define HYM8563_MIN 0x03 #define HYM8563_MIN_MASK 0x7f #define HYM8563_HOUR 0x04 #define HYM8563_HOUR_MASK 0x3f #define HYM8563_DAY 0x05 #define HYM8563_DAY_MASK 0x3f #define HYM8563_WEEKDAY 0x06 #define HYM8563_WEEKDAY_MASK 0x07 #define HYM8563_MONTH 0x07 #define HYM8563_MONTH_CENTURY RT_BIT(7) #define HYM8563_MONTH_MASK 0x1f #define HYM8563_YEAR 0x08 #define HYM8563_ALM_MIN 0x09 #define HYM8563_ALM_HOUR 0x0a #define HYM8563_ALM_DAY 0x0b #define HYM8563_ALM_WEEK 0x0c /* Each alarm check can be disabled by setting this bit in the register */ #define HYM8563_ALM_BIT_DISABLE RT_BIT(7) #define HYM8563_CLKOUT 0x0d #define HYM8563_CLKOUT_ENABLE RT_BIT(7) #define HYM8563_CLKOUT_32768 0 #define HYM8563_CLKOUT_1024 1 #define HYM8563_CLKOUT_32 2 #define HYM8563_CLKOUT_1 3 #define HYM8563_CLKOUT_MASK 3 #define HYM8563_TMR_CTL 0x0e #define HYM8563_TMR_CTL_ENABLE RT_BIT(7) #define HYM8563_TMR_CTL_4096 0 #define HYM8563_TMR_CTL_64 1 #define HYM8563_TMR_CTL_1 2 #define HYM8563_TMR_CTL_1_60 3 #define HYM8563_TMR_CTL_MASK 3 #define HYM8563_TMR_CNT 0x0f struct hym8563_rtc { struct rt_device parent; struct rt_clk_node clkout_hw; struct rt_clk_cell cell; struct rt_clk_cell *cells[1]; int irq; struct rt_i2c_client *client; struct rt_thread *irq_thread; struct rt_rtc_wkalarm wkalarm; }; #define raw_to_hym8563_rtc(raw) rt_container_of(raw, struct hym8563_rtc, parent) #define raw_to_hym8563_clkout(raw) rt_container_of(raw, struct hym8563_rtc, clkout_hw) static rt_int32_t i2c_smbus_read_byte_data(struct rt_i2c_client *client, rt_uint8_t command) { rt_int32_t res; rt_uint8_t ret = 0; struct rt_i2c_msg msg[2]; msg[0].buf = &command; msg[0].addr = client->client_addr; msg[0].len = 1; msg[0].flags = RT_I2C_WR; msg[1].buf = &ret; msg[1].addr = client->client_addr; msg[1].len = 1; msg[1].flags = RT_I2C_RD; res = rt_i2c_transfer(client->bus, msg, 2); return res == 2 ? ret : res; } static rt_int32_t i2c_smbus_write_byte_data(struct rt_i2c_client *client, rt_uint8_t command, rt_uint8_t value) { rt_int32_t res; struct rt_i2c_msg msg[1]; rt_uint8_t data[2] = { command, value }; msg[0].buf = data; msg[0].addr = client->client_addr; msg[0].len = 2; msg[0].flags = RT_I2C_WR; res = rt_i2c_transfer(client->bus, msg, 1); return res == 1 ? 0 : res; } /* Returns the number of read bytes */ static rt_int32_t i2c_smbus_read_i2c_block_data(struct rt_i2c_client *client, rt_uint8_t command, rt_uint8_t length, rt_uint8_t *values) { struct rt_i2c_msg msg[2]; msg[0].buf = &command; msg[0].addr = client->client_addr; msg[0].len = 1; msg[0].flags = RT_I2C_WR; msg[1].buf = values; msg[1].addr = client->client_addr; msg[1].len = length; msg[1].flags = RT_I2C_RD; return rt_i2c_transfer(client->bus, msg, 2); } static rt_int32_t i2c_smbus_write_i2c_block_data(struct rt_i2c_client *client, rt_uint8_t command, rt_uint8_t length, const rt_uint8_t *values) { rt_uint8_t data[32]; struct rt_i2c_msg msg[1]; length = rt_min_t(rt_uint8_t, length, RT_ARRAY_SIZE(data) - 1); data[0] = command; rt_memcpy(&data[1], values, length); msg[0].buf = data; msg[0].addr = client->client_addr; msg[0].len = length + 1; msg[0].flags = RT_I2C_WR; return rt_i2c_transfer(client->bus, msg, 1); } static void hym8563_rtc_read_time(struct hym8563_rtc *hym8563, time_t *sec) { struct tm tm; rt_uint8_t buf[7]; if (i2c_smbus_read_i2c_block_data(hym8563->client, HYM8563_SEC, 7, buf) < 0) { return; } if (buf[0] & HYM8563_SEC_VL) { LOG_D("no valid clock/calendar values available"); } tm.tm_sec = rt_bcd2bin(buf[0] & HYM8563_SEC_MASK); tm.tm_min = rt_bcd2bin(buf[1] & HYM8563_MIN_MASK); tm.tm_hour = rt_bcd2bin(buf[2] & HYM8563_HOUR_MASK); tm.tm_mday = rt_bcd2bin(buf[3] & HYM8563_DAY_MASK); tm.tm_wday = rt_bcd2bin(buf[4] & HYM8563_WEEKDAY_MASK); /* 0 = Sun */ tm.tm_mon = rt_bcd2bin(buf[5] & HYM8563_MONTH_MASK) - 1; /* 0 = Jan */ tm.tm_year = rt_bcd2bin(buf[6]) + 100; *sec = timegm(&tm); } static void hym8563_rtc_set_time(struct hym8563_rtc *hym8563, time_t *sec) { struct tm *tm; rt_uint8_t buf[7]; struct rt_i2c_client *client = hym8563->client; tm = localtime(sec); /* Years >= 2100 are to far in the future, 19XX is to early */ if (tm->tm_year < 100 || tm->tm_year >= 200) { return; } buf[0] = rt_bin2bcd(tm->tm_sec); buf[1] = rt_bin2bcd(tm->tm_min); buf[2] = rt_bin2bcd(tm->tm_hour); buf[3] = rt_bin2bcd(tm->tm_mday); buf[4] = rt_bin2bcd(tm->tm_wday); buf[5] = rt_bin2bcd(tm->tm_mon + 1); /* * While the HYM8563 has a century flag in the month register, * it does not seem to carry it over a subsequent write/read. * So we'll limit ourself to 100 years, starting at 2000 for now. */ buf[6] = rt_bin2bcd(tm->tm_year - 100); /* CTL1 only contains TEST-mode bits apart from stop, so no need to read the value first */ if (i2c_smbus_write_byte_data(client, HYM8563_CTL1, HYM8563_CTL1_STOP) < 0) { return; } if (i2c_smbus_write_i2c_block_data(client, HYM8563_SEC, 7, buf) < 0) { return; } if (i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0) < 0) { return; } } static int hym8563_rtc_alarm_irq_enable(struct hym8563_rtc *hym8563, rt_bool_t enabled) { int data; struct rt_i2c_client *client = hym8563->client; data = i2c_smbus_read_byte_data(client, HYM8563_CTL2); if (data < 0) { return data; } if (enabled) { data |= HYM8563_CTL2_AIE; } else { data &= ~HYM8563_CTL2_AIE; } return i2c_smbus_write_byte_data(client, HYM8563_CTL2, data); }; static int hym8563_rtc_read_alarm(struct hym8563_rtc *hym8563, struct rt_rtc_wkalarm *alarm) { int res; rt_uint8_t buf[4]; struct rt_i2c_client *client = hym8563->client; res = i2c_smbus_read_i2c_block_data(client, HYM8563_ALM_MIN, 4, buf); if (res < 0) { return res; } /* The alarm only has a minute accuracy */ alarm->tm_sec = 0; alarm->tm_min = (buf[0] & HYM8563_ALM_BIT_DISABLE) ? -1 : rt_bcd2bin(buf[0] & HYM8563_MIN_MASK); alarm->tm_hour = (buf[1] & HYM8563_ALM_BIT_DISABLE) ? -1 : rt_bcd2bin(buf[1] & HYM8563_HOUR_MASK); alarm->tm_mday = (buf[2] & HYM8563_ALM_BIT_DISABLE) ? -1 : rt_bcd2bin(buf[2] & HYM8563_DAY_MASK); /* * alarm->tm_wday = (buf[3] & HYM8563_ALM_BIT_DISABLE) ? * -1 : rt_bcd2bin(buf[3] & HYM8563_WEEKDAY_MASK); */ res = i2c_smbus_read_byte_data(client, HYM8563_CTL2); if (res < 0) { return res; } alarm->enable = res & HYM8563_CTL2_AIE ? RT_TRUE : RT_FALSE; return 0; } static int hym8563_rtc_set_alarm(struct hym8563_rtc *hym8563, struct rt_rtc_wkalarm *alarm) { int res; rt_uint8_t buf[4]; struct rt_i2c_client *client = hym8563->client; struct rt_rtc_wkalarm *wkalarm = &hym8563->wkalarm; res = i2c_smbus_read_byte_data(client, HYM8563_CTL2); if (res < 0) { return res; } res &= ~HYM8563_CTL2_AIE; res = i2c_smbus_write_byte_data(client, HYM8563_CTL2, res); if (res < 0) { return res; } buf[0] = (alarm->tm_min < 60 && alarm->tm_min >= 0) ? rt_bin2bcd(alarm->tm_min) : HYM8563_ALM_BIT_DISABLE; buf[1] = (alarm->tm_hour < 24 && alarm->tm_hour >= 0) ? rt_bin2bcd(alarm->tm_hour) : HYM8563_ALM_BIT_DISABLE; buf[2] = (alarm->tm_mday <= 31 && alarm->tm_mday >= 1) ? rt_bin2bcd(alarm->tm_mday) : HYM8563_ALM_BIT_DISABLE; /* * buf[3] = (alarm->tm_wday < 7 && alarm->tm_wday >= 0) ? * rt_bin2bcd(alarm->tm_wday) : HYM8563_ALM_BIT_DISABLE; */ res = i2c_smbus_write_i2c_block_data(client, HYM8563_ALM_MIN, 4, buf); if (res < 0) { return res; } res = hym8563_rtc_alarm_irq_enable(hym8563, alarm->enable); if (!(res < 0)) { wkalarm->enable = alarm->enable; wkalarm->tm_hour = alarm->tm_hour; wkalarm->tm_min = alarm->tm_min; wkalarm->tm_sec = alarm->tm_sec; } return res; } static rt_err_t hym8563_rtc_control(rt_device_t dev, int cmd, void *args) { rt_err_t err = RT_EOK; struct hym8563_rtc *hym8563 = raw_to_hym8563_rtc(dev); if (!args) { return -RT_EINVAL; } switch (cmd) { case RT_DEVICE_CTRL_RTC_GET_TIME: hym8563_rtc_read_time(hym8563, args); break; case RT_DEVICE_CTRL_RTC_SET_TIME: hym8563_rtc_set_time(hym8563, args); break; case RT_DEVICE_CTRL_RTC_GET_TIMEVAL: hym8563_rtc_read_time(hym8563, (time_t *)&((struct timeval *)args)->tv_sec); break; case RT_DEVICE_CTRL_RTC_SET_TIMEVAL: hym8563_rtc_set_time(hym8563, (time_t *)&((struct timeval *)args)->tv_sec); break; case RT_DEVICE_CTRL_RTC_GET_ALARM: err = hym8563_rtc_read_alarm(hym8563, args); break; case RT_DEVICE_CTRL_RTC_SET_ALARM: err = hym8563_rtc_set_alarm(hym8563, args); break; default: err = -RT_EINVAL; break; } return err; } #ifdef RT_USING_DEVICE_OPS const static struct rt_device_ops hym8563_rtc_ops = { .control = hym8563_rtc_control, }; #endif static void hym8563_rtc_thread_isr(void *param) { int data, res; struct hym8563_rtc *hym8563 = param; struct rt_i2c_client *client = hym8563->client; while (RT_TRUE) { rt_thread_suspend(hym8563->irq_thread); rt_schedule(); data = i2c_smbus_read_byte_data(client, HYM8563_CTL2); if (data < 0) { LOG_E("IRQ: error %sing i2c data %d", "read", data); return; } data &= ~HYM8563_CTL2_AF; res = i2c_smbus_write_byte_data(client, HYM8563_CTL2, data); if (res < 0) { LOG_E("IRQ: error %sing i2c data %d", "writ", res); return; } rt_alarm_update(&hym8563->parent, 1); } } static void hym8563_rtc_isr(int irqno, void *param) { struct hym8563_rtc *hym8563 = param; rt_thread_resume(hym8563->irq_thread); } static const int clkout_rates[] = { 32768, 1024, 32, 1, }; static int hym8563_clkout_control(struct hym8563_rtc *hym8563, rt_bool_t enable) { int res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT); if (res < 0) { return res; } if (enable) { res |= HYM8563_CLKOUT_ENABLE; } else { res &= ~HYM8563_CLKOUT_ENABLE; } return i2c_smbus_write_byte_data(hym8563->client, HYM8563_CLKOUT, res); } static rt_err_t hym8563_clkout_prepare(struct rt_clk_cell *cell) { struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np); return hym8563_clkout_control(hym8563, RT_TRUE); } static void hym8563_clkout_unprepare(struct rt_clk_cell *cell) { struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np); hym8563_clkout_control(hym8563, RT_FALSE); } static rt_bool_t hym8563_clkout_is_prepared(struct rt_clk_cell *cell) { int res; struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np); res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT); if (res < 0) { return RT_FALSE; } return !!(res & HYM8563_CLKOUT_ENABLE); } static rt_ubase_t hym8563_clkout_recalc_rate(struct rt_clk_cell *cell, rt_ubase_t parent_rate) { int res; struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np); res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT); if (res < 0) { return res; } res &= HYM8563_CLKOUT_MASK; return clkout_rates[res]; } static rt_base_t hym8563_clkout_round_rate(struct rt_clk_cell *cell, rt_ubase_t drate, rt_ubase_t *prate) { for (int i = 0; i < RT_ARRAY_SIZE(clkout_rates); ++i) { if (clkout_rates[i] <= drate) { return clkout_rates[i]; } } return 0; } static rt_err_t hym8563_clkout_set_rate(struct rt_clk_cell *cell, rt_ubase_t rate, rt_ubase_t parent_rate) { int res; struct hym8563_rtc *hym8563 = raw_to_hym8563_clkout(cell->clk_np); res = i2c_smbus_read_byte_data(hym8563->client, HYM8563_CLKOUT); if (res < 0) { return res; } for (int i = 0; i < RT_ARRAY_SIZE(clkout_rates); ++i) { if (clkout_rates[i] == rate) { res &= ~HYM8563_CLKOUT_MASK; res |= i; res = i2c_smbus_write_byte_data(hym8563->client, HYM8563_CLKOUT, res); return res >= 0 ? RT_EOK : res; } } return -RT_EINVAL; } static const struct rt_clk_ops hym8563_clkout_ops = { .prepare = hym8563_clkout_prepare, .unprepare = hym8563_clkout_unprepare, .is_prepared = hym8563_clkout_is_prepared, .recalc_rate = hym8563_clkout_recalc_rate, .round_rate = hym8563_clkout_round_rate, .set_rate = hym8563_clkout_set_rate, }; static void hym8563_clkout_register_clk(struct hym8563_rtc *hym8563, struct rt_device *dev) { if (i2c_smbus_write_byte_data(hym8563->client, HYM8563_CLKOUT, 0) < 0) { return; } hym8563->cells[0] = &hym8563->cell; hym8563->cell.name = "hym8563-clkout"; hym8563->cell.ops = &hym8563_clkout_ops; rt_dm_dev_prop_read_string(dev, "clock-output-names", &hym8563->cell.name); hym8563->clkout_hw.dev = dev; hym8563->clkout_hw.cells_nr = 1; hym8563->clkout_hw.cells = hym8563->cells; if (rt_clk_register(&hym8563->clkout_hw)) { return; } } static rt_err_t hym8563_init_device(struct rt_i2c_client *client) { int res; /* Clear stop flag if present */ res = i2c_smbus_write_byte_data(client, HYM8563_CTL1, 0); if (res < 0) { return res; } res = i2c_smbus_read_byte_data(client, HYM8563_CTL2); if (res < 0) { return res; } /* Disable alarm and timer interrupts */ res &= ~HYM8563_CTL2_AIE; res &= ~HYM8563_CTL2_TIE; /* Clear any pending alarm and timer flags */ if (res & HYM8563_CTL2_AF) { res &= ~HYM8563_CTL2_AF; } if (res & HYM8563_CTL2_TF) { res &= ~HYM8563_CTL2_TF; } res &= ~HYM8563_CTL2_TI_TP; return i2c_smbus_write_byte_data(client, HYM8563_CTL2, res); } static rt_err_t hym8563_rtc_probe(struct rt_i2c_client *client) { rt_err_t err; rt_int32_t res; const char *dev_name; struct rt_device *dev = &client->parent; struct hym8563_rtc *hym8563 = rt_calloc(1, sizeof(*hym8563)); if (!hym8563) { return -RT_ENOMEM; } if ((res = hym8563_init_device(client)) < 0) { err = res; goto _fail; } hym8563->irq = rt_dm_dev_get_irq(dev, 0); hym8563->client = client; /* check state of calendar information */ if ((res = i2c_smbus_read_byte_data(client, HYM8563_SEC)) < 0) { err = res; goto _fail; } LOG_D("rtc information is %s", (res & HYM8563_SEC_VL) ? "invalid" : "valid"); if (hym8563->irq >= 0) { hym8563->irq_thread = rt_thread_create("rtc-hym8563", &hym8563_rtc_thread_isr, hym8563, DM_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 2, 10); if (!hym8563->irq_thread) { err = -RT_ERROR; LOG_E("Create RTC IRQ thread fail"); goto _fail; } rt_thread_startup(hym8563->irq_thread); rt_hw_interrupt_install(hym8563->irq, hym8563_rtc_isr, hym8563, "rtc-hym8563"); rt_hw_interrupt_umask(hym8563->irq); } dev->user_data = hym8563; hym8563->parent.type = RT_Device_Class_RTC; #ifdef RT_USING_DEVICE_OPS hym8563->parent.ops = &hym8563_rtc_ops; #else hym8563->parent.control = hym8563_rtc_control; #endif rtc_dev_set_name(&hym8563->parent); dev_name = rt_dm_dev_get_name(&hym8563->parent); rt_device_register(&hym8563->parent, dev_name, RT_DEVICE_FLAG_RDWR); hym8563_clkout_register_clk(hym8563, dev); return RT_EOK; _fail: if (hym8563->irq_thread) { rt_thread_delete(hym8563->irq_thread); } rt_free(hym8563); return err; } static rt_err_t hym8563_rtc_remove(struct rt_i2c_client *client) { struct hym8563_rtc *hym8563 = client->parent.user_data; rt_dm_dev_unbind_fwdata(&client->parent, RT_NULL); if (hym8563->irq >= 0) { if (hym8563->wkalarm.enable) { hym8563_rtc_alarm_irq_enable(hym8563, RT_FALSE); } rt_hw_interrupt_mask(hym8563->irq); rt_pic_detach_irq(hym8563->irq, hym8563); rt_thread_delete(hym8563->irq_thread); } if (hym8563->cells[0]) { rt_clk_unregister(&hym8563->clkout_hw); } rt_device_unregister(&hym8563->parent); rt_free(hym8563); return RT_EOK; } static const struct rt_i2c_device_id hym8563_rtc_ids[] = { { .name = "hym8563" }, { /* sentinel */ }, }; static const struct rt_ofw_node_id hym8563_rtc_ofw_ids[] = { { .compatible = "haoyu,hym8563" }, { /* sentinel */ }, }; static struct rt_i2c_driver hym8563_rtc_driver = { .ids = hym8563_rtc_ids, .ofw_ids = hym8563_rtc_ofw_ids, .probe = hym8563_rtc_probe, .remove = hym8563_rtc_remove, }; RT_I2C_DRIVER_EXPORT(hym8563_rtc_driver);