|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Real Time Clock (RTC) Driver for sd3078 | 
|  | * Copyright (C) 2018 Zoro Li | 
|  | */ | 
|  |  | 
|  | #include <linux/bcd.h> | 
|  | #include <linux/i2c.h> | 
|  | #include <linux/module.h> | 
|  | #include <linux/regmap.h> | 
|  | #include <linux/rtc.h> | 
|  | #include <linux/slab.h> | 
|  |  | 
|  | #define SD3078_REG_SC			0x00 | 
|  | #define SD3078_REG_MN			0x01 | 
|  | #define SD3078_REG_HR			0x02 | 
|  | #define SD3078_REG_DW			0x03 | 
|  | #define SD3078_REG_DM			0x04 | 
|  | #define SD3078_REG_MO			0x05 | 
|  | #define SD3078_REG_YR			0x06 | 
|  |  | 
|  | #define SD3078_REG_CTRL1		0x0f | 
|  | #define SD3078_REG_CTRL2		0x10 | 
|  | #define SD3078_REG_CTRL3		0x11 | 
|  |  | 
|  | #define KEY_WRITE1		0x80 | 
|  | #define KEY_WRITE2		0x04 | 
|  | #define KEY_WRITE3		0x80 | 
|  |  | 
|  | #define NUM_TIME_REGS   (SD3078_REG_YR - SD3078_REG_SC + 1) | 
|  |  | 
|  | /* | 
|  | * The sd3078 has write protection | 
|  | * and we can choose whether or not to use it. | 
|  | * Write protection is turned off by default. | 
|  | */ | 
|  | #define WRITE_PROTECT_EN	0 | 
|  |  | 
|  | struct sd3078 { | 
|  | struct rtc_device	*rtc; | 
|  | struct regmap		*regmap; | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * In order to prevent arbitrary modification of the time register, | 
|  | * when modification of the register, | 
|  | * the "write" bit needs to be written in a certain order. | 
|  | * 1. set WRITE1 bit | 
|  | * 2. set WRITE2 bit | 
|  | * 3. set WRITE3 bit | 
|  | */ | 
|  | static void sd3078_enable_reg_write(struct sd3078 *sd3078) | 
|  | { | 
|  | regmap_update_bits(sd3078->regmap, SD3078_REG_CTRL2, | 
|  | KEY_WRITE1, KEY_WRITE1); | 
|  | regmap_update_bits(sd3078->regmap, SD3078_REG_CTRL1, | 
|  | KEY_WRITE2, KEY_WRITE2); | 
|  | regmap_update_bits(sd3078->regmap, SD3078_REG_CTRL1, | 
|  | KEY_WRITE3, KEY_WRITE3); | 
|  | } | 
|  |  | 
|  | #if WRITE_PROTECT_EN | 
|  | /* | 
|  | * In order to prevent arbitrary modification of the time register, | 
|  | * we should disable the write function. | 
|  | * when disable write, | 
|  | * the "write" bit needs to be clear in a certain order. | 
|  | * 1. clear WRITE2 bit | 
|  | * 2. clear WRITE3 bit | 
|  | * 3. clear WRITE1 bit | 
|  | */ | 
|  | static void sd3078_disable_reg_write(struct sd3078 *sd3078) | 
|  | { | 
|  | regmap_update_bits(sd3078->regmap, SD3078_REG_CTRL1, | 
|  | KEY_WRITE2, 0); | 
|  | regmap_update_bits(sd3078->regmap, SD3078_REG_CTRL1, | 
|  | KEY_WRITE3, 0); | 
|  | regmap_update_bits(sd3078->regmap, SD3078_REG_CTRL2, | 
|  | KEY_WRITE1, 0); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | static int sd3078_rtc_read_time(struct device *dev, struct rtc_time *tm) | 
|  | { | 
|  | unsigned char hour; | 
|  | unsigned char rtc_data[NUM_TIME_REGS] = {0}; | 
|  | struct i2c_client *client = to_i2c_client(dev); | 
|  | struct sd3078 *sd3078 = i2c_get_clientdata(client); | 
|  | int ret; | 
|  |  | 
|  | ret = regmap_bulk_read(sd3078->regmap, SD3078_REG_SC, rtc_data, | 
|  | NUM_TIME_REGS); | 
|  | if (ret < 0) { | 
|  | dev_err(dev, "reading from RTC failed with err:%d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | tm->tm_sec	= bcd2bin(rtc_data[SD3078_REG_SC] & 0x7F); | 
|  | tm->tm_min	= bcd2bin(rtc_data[SD3078_REG_MN] & 0x7F); | 
|  |  | 
|  | /* | 
|  | * The sd3078 supports 12/24 hour mode. | 
|  | * When getting time, | 
|  | * we need to convert the 12 hour mode to the 24 hour mode. | 
|  | */ | 
|  | hour = rtc_data[SD3078_REG_HR]; | 
|  | if (hour & 0x80) /* 24H MODE */ | 
|  | tm->tm_hour = bcd2bin(rtc_data[SD3078_REG_HR] & 0x3F); | 
|  | else if (hour & 0x20) /* 12H MODE PM */ | 
|  | tm->tm_hour = bcd2bin(rtc_data[SD3078_REG_HR] & 0x1F) + 12; | 
|  | else /* 12H MODE AM */ | 
|  | tm->tm_hour = bcd2bin(rtc_data[SD3078_REG_HR] & 0x1F); | 
|  |  | 
|  | tm->tm_mday = bcd2bin(rtc_data[SD3078_REG_DM] & 0x3F); | 
|  | tm->tm_wday = rtc_data[SD3078_REG_DW] & 0x07; | 
|  | tm->tm_mon	= bcd2bin(rtc_data[SD3078_REG_MO] & 0x1F) - 1; | 
|  | tm->tm_year = bcd2bin(rtc_data[SD3078_REG_YR]) + 100; | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int sd3078_rtc_set_time(struct device *dev, struct rtc_time *tm) | 
|  | { | 
|  | unsigned char rtc_data[NUM_TIME_REGS]; | 
|  | struct i2c_client *client = to_i2c_client(dev); | 
|  | struct sd3078 *sd3078 = i2c_get_clientdata(client); | 
|  | int ret; | 
|  |  | 
|  | rtc_data[SD3078_REG_SC] = bin2bcd(tm->tm_sec); | 
|  | rtc_data[SD3078_REG_MN] = bin2bcd(tm->tm_min); | 
|  | rtc_data[SD3078_REG_HR] = bin2bcd(tm->tm_hour) | 0x80; | 
|  | rtc_data[SD3078_REG_DM] = bin2bcd(tm->tm_mday); | 
|  | rtc_data[SD3078_REG_DW] = tm->tm_wday & 0x07; | 
|  | rtc_data[SD3078_REG_MO] = bin2bcd(tm->tm_mon) + 1; | 
|  | rtc_data[SD3078_REG_YR] = bin2bcd(tm->tm_year - 100); | 
|  |  | 
|  | #if WRITE_PROTECT_EN | 
|  | sd3078_enable_reg_write(sd3078); | 
|  | #endif | 
|  |  | 
|  | ret = regmap_bulk_write(sd3078->regmap, SD3078_REG_SC, rtc_data, | 
|  | NUM_TIME_REGS); | 
|  | if (ret < 0) { | 
|  | dev_err(dev, "writing to RTC failed with err:%d\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | #if WRITE_PROTECT_EN | 
|  | sd3078_disable_reg_write(sd3078); | 
|  | #endif | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct rtc_class_ops sd3078_rtc_ops = { | 
|  | .read_time	= sd3078_rtc_read_time, | 
|  | .set_time	= sd3078_rtc_set_time, | 
|  | }; | 
|  |  | 
|  | static const struct regmap_config regmap_config = { | 
|  | .reg_bits = 8, | 
|  | .val_bits = 8, | 
|  | .max_register = 0x11, | 
|  | }; | 
|  |  | 
|  | static int sd3078_probe(struct i2c_client *client, | 
|  | const struct i2c_device_id *id) | 
|  | { | 
|  | int ret; | 
|  | struct sd3078 *sd3078; | 
|  |  | 
|  | if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) | 
|  | return -ENODEV; | 
|  |  | 
|  | sd3078 = devm_kzalloc(&client->dev, sizeof(*sd3078), GFP_KERNEL); | 
|  | if (!sd3078) | 
|  | return -ENOMEM; | 
|  |  | 
|  | sd3078->regmap = devm_regmap_init_i2c(client, ®map_config); | 
|  | if (IS_ERR(sd3078->regmap)) { | 
|  | dev_err(&client->dev, "regmap allocation failed\n"); | 
|  | return PTR_ERR(sd3078->regmap); | 
|  | } | 
|  |  | 
|  | i2c_set_clientdata(client, sd3078); | 
|  |  | 
|  | sd3078->rtc = devm_rtc_allocate_device(&client->dev); | 
|  | if (IS_ERR(sd3078->rtc)) | 
|  | return PTR_ERR(sd3078->rtc); | 
|  |  | 
|  | sd3078->rtc->ops = &sd3078_rtc_ops; | 
|  | sd3078->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000; | 
|  | sd3078->rtc->range_max = RTC_TIMESTAMP_END_2099; | 
|  |  | 
|  | ret = rtc_register_device(sd3078->rtc); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | sd3078_enable_reg_write(sd3078); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static const struct i2c_device_id sd3078_id[] = { | 
|  | {"sd3078", 0}, | 
|  | { } | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(i2c, sd3078_id); | 
|  |  | 
|  | static const struct of_device_id rtc_dt_match[] = { | 
|  | { .compatible = "whwave,sd3078" }, | 
|  | {}, | 
|  | }; | 
|  | MODULE_DEVICE_TABLE(of, rtc_dt_match); | 
|  |  | 
|  | static struct i2c_driver sd3078_driver = { | 
|  | .driver     = { | 
|  | .name   = "sd3078", | 
|  | .of_match_table = of_match_ptr(rtc_dt_match), | 
|  | }, | 
|  | .probe      = sd3078_probe, | 
|  | .id_table   = sd3078_id, | 
|  | }; | 
|  |  | 
|  | module_i2c_driver(sd3078_driver); | 
|  |  | 
|  | MODULE_AUTHOR("Dianlong Li <long17.cool@163.com>"); | 
|  | MODULE_DESCRIPTION("SD3078 RTC driver"); | 
|  | MODULE_LICENSE("GPL v2"); |