From da12f900525dd31fe46bfc3d0cfdac8d83bded4a Mon Sep 17 00:00:00 2001 From: Luochongjun <1464691921@qq.com> Date: Tue, 31 Oct 2017 16:02:39 +0800 Subject: [PATCH] add rtc-sd2068 driver --- package/kernel/rtc-sd2068/Makefile | 39 ++ package/kernel/rtc-sd2068/src/Makefile | 17 + package/kernel/rtc-sd2068/src/rtc-sd2068.c | 511 +++++++++++++++++++++ 3 files changed, 567 insertions(+) create mode 100644 package/kernel/rtc-sd2068/Makefile create mode 100644 package/kernel/rtc-sd2068/src/Makefile create mode 100644 package/kernel/rtc-sd2068/src/rtc-sd2068.c diff --git a/package/kernel/rtc-sd2068/Makefile b/package/kernel/rtc-sd2068/Makefile new file mode 100644 index 0000000000..3cc6720572 --- /dev/null +++ b/package/kernel/rtc-sd2068/Makefile @@ -0,0 +1,39 @@ +# +# Copyright (C) 2017 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk +include $(INCLUDE_DIR)/kernel.mk + +PKG_NAME:=rtc-sd2068 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk + +define KernelPackage/rtc-sd2068 + SUBMENU:=Other modules + DEPENDS:=@TARGET_ar71xx + KCONFIG:=CONFIG_RTC_CLASS=y + TITLE:=Driver for RTC SD2068 + AUTOLOAD:=$(call AutoLoad,70,rtc-sd2068) + FILES:=$(PKG_BUILD_DIR)/rtc-sd2068.ko +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C "$(LINUX_DIR)" \ + CROSS_COMPILE="$(TARGET_CROSS)" \ + ARCH="$(LINUX_KARCH)" \ + SUBDIRS="$(PKG_BUILD_DIR)" \ + EXTRA_CFLAGS="$(BUILDFLAGS)" \ + modules +endef + +$(eval $(call KernelPackage,rtc-sd2068)) diff --git a/package/kernel/rtc-sd2068/src/Makefile b/package/kernel/rtc-sd2068/src/Makefile new file mode 100644 index 0000000000..15b4f4961c --- /dev/null +++ b/package/kernel/rtc-sd2068/src/Makefile @@ -0,0 +1,17 @@ +# +# Makefile for Real Time Clock driver for SD2068 +# +# Copyright (C) 2017 OpenWrt.org +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version +# 2 of the License, or (at your option) any later version. +# + +obj-m := rtc-sd2068.o + +ifeq ($(MAKING_MODULES),1) + +-include $(TOPDIR)/Rules.make +endif diff --git a/package/kernel/rtc-sd2068/src/rtc-sd2068.c b/package/kernel/rtc-sd2068/src/rtc-sd2068.c new file mode 100644 index 0000000000..e537ca700d --- /dev/null +++ b/package/kernel/rtc-sd2068/src/rtc-sd2068.c @@ -0,0 +1,511 @@ +/* + * RTC client/driver for the Whwave SD2068 Real-Time Clock over I2C + * + * Copyright (C) 2013 Tang, Haifeng . + * + * base on ds3232 + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ +/* + * It would be more efficient to use i2c msgs/i2c_transfer directly but, as + * recommened in .../Documentation/i2c/writing-clients section + * "Sending and receiving", using SMBus level communication is preferred. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define SD2068_SECONDS 0x00 +#define SD2068_MINUTES 0x01 +#define SD2068_HOURS 0x02 +#define SD2068_AMPM 0x02 +#define SD2068_DAY 0x03 +#define SD2068_DATE 0x04 +#define SD2068_MONTH 0x05 +#define SD2068_YEAR 0x06 + +#define SD2068_ALARM 0x07 /* Alarm 1 BASE */ + +#define SD2068_ALARM_EN 0x0E +# define SD2068_ALARM_EAY 0x40 +# define SD2068_ALARM_EAMO 0x20 +# define SD2068_ALARM_EAD 0x10 +# define SD2068_ALARM_EAW 0x08 +# define SD2068_ALARM_EAH 0x04 +# define SD2068_ALARM_EAMN 0x02 +# define SD2068_ALARM_EAS 0x01 + +#define SD2068_CTR1 0x0F /* Control register 1 */ +# define SD2068_CTR1_WRTC3 0x80 +# define SD2068_CTR1_INTAF 0x20 +# define SD2068_CTR1_INTDF 0x10 +# define SD2068_CTR1_WRTC2 0x04 +# define SD2068_CTR1_RTCF 0x01 + +#define SD2068_CTR2 0x10 +# define SD2068_CTR2_WRTC1 0x80 +# define SD2068_CTR2_IM 0x40 +# define SD2068_CTR2_INTS1 0x20 +# define SD2068_CTR2_INTS0 0x10 +# define SD2068_CTR2_FOBAT 0x08 +# define SD2068_CTR2_INTDE 0x04 +# define SD2068_CTR2_INTAE 0x02 +# define SD2068_CTR2_INTFE 0x01 + +#define SD2068_CTR3 0x11 +# define SD2068_CTR3_ARST 0x80 +# define SD2068_CTR3_TDS1 0x20 +# define SD2068_CTR3_TDS0 0x10 +# define SD2068_CTR3_FS3 0x08 +# define SD2068_CTR3_FS2 0x04 +# define SD2068_CTR3_FS1 0x02 +# define SD2068_CTR3_FS0 0x01 + +#define SD2068_TIME_ADJ 0x12 + +struct sd2068 { + struct i2c_client *client; + struct rtc_device *rtc; + struct work_struct work; + + /* The mutex protects alarm operations, and prevents a race + * between the enable_irq() in the workqueue and the free_irq() + * in the remove function. + */ + struct mutex mutex; + int exiting; +}; + +static void sd2068_write_enable(struct i2c_client *client) +{ + char ret; + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); + ret = ret | SD2068_CTR2_WRTC1; + i2c_smbus_write_byte_data(client, SD2068_CTR2, ret); + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR1); + ret = ret | SD2068_CTR1_WRTC3 | SD2068_CTR1_WRTC2; + i2c_smbus_write_byte_data(client, SD2068_CTR1, ret); +} + +static void sd2068_write_disable(struct i2c_client *client) +{ + char ret; + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR1); + ret = ret & (~SD2068_CTR1_WRTC3) & (~SD2068_CTR1_WRTC2); + i2c_smbus_write_byte_data(client, SD2068_CTR1, ret); + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); + ret = ret & (~SD2068_CTR2_WRTC1); + i2c_smbus_write_byte_data(client, SD2068_CTR2, ret); +} + +static void sd2068_hw_init(struct i2c_client *client) +{ + char ret; + + sd2068_write_enable(client); + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); + ret = ret & (~SD2068_CTR2_IM); /* 只使用单事件报警 */ + ret = ret & ((~SD2068_CTR2_INTS1) | SD2068_CTR2_INTS0); + ret = ret & (~SD2068_CTR2_FOBAT); + ret = ret & (((~SD2068_CTR2_INTDE) | SD2068_CTR2_INTAE) & (~SD2068_CTR2_INTFE)); + i2c_smbus_write_byte_data(client, SD2068_CTR2, ret); + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR3); + ret = ret & (~SD2068_CTR3_ARST); + i2c_smbus_write_byte_data(client, SD2068_CTR3, ret); + + sd2068_write_disable(client); +} + +static int sd2068_read_time(struct device *dev, struct rtc_time *time) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret; + u8 buf[7]; + unsigned char year, month, day, hour, minute, second; + unsigned char week, twelve_hr, am_pm; + + ret = i2c_smbus_read_i2c_block_data(client, SD2068_SECONDS, 7, buf); + if (ret < 0) + return ret; + if (ret < 7) + return -EIO; + + second = buf[0]; + minute = buf[1]; + hour = buf[2]; + week = buf[3]; + day = buf[4]; + month = buf[5]; + year = buf[6]; + + /* Extract additional information for AM/PM */ + twelve_hr = hour & 0x80; + am_pm = hour & 0x20; + + /* Write to rtc_time structure */ + time->tm_sec = bcd2bin(second & 0x7f); + time->tm_min = bcd2bin(minute & 0x7f); + if (twelve_hr) { + time->tm_hour = bcd2bin(hour & 0x3f); + } else { + /* Convert to 24 hr */ + if (am_pm) + time->tm_hour = bcd2bin(hour & 0x1f) + 12; + else + time->tm_hour = bcd2bin(hour & 0x1f); + } + + time->tm_wday = bcd2bin(week & 0x07); + time->tm_mday = bcd2bin(day & 0x3f); + /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */ + time->tm_mon = bcd2bin(month & 0x7F) - 1; + time->tm_year = bcd2bin(year); + if (time->tm_year < 70) + time->tm_year += 100; + + dev_dbg(dev, "%s secs=%d, mins=%d, " + "hours=%d, mday=%d, mon=%d, year=%d, wday=%d\n", + "read", time->tm_sec, time->tm_min, + time->tm_hour, time->tm_mday, + time->tm_mon, time->tm_year, time->tm_wday); + + return rtc_valid_tm(time); +} + +static int sd2068_set_time(struct device *dev, struct rtc_time *time) +{ + struct i2c_client *client = to_i2c_client(dev); + u8 buf[7]; + + /* Extract time from rtc_time and load into sd2068*/ + sd2068_write_enable(client); + + buf[0] = bin2bcd(time->tm_sec); + buf[1] = bin2bcd(time->tm_min); + buf[2] = bin2bcd(time->tm_hour) | 0x80; /* only 24 hr? */ + buf[3] = bin2bcd(time->tm_wday); + buf[4] = bin2bcd(time->tm_mday); /* Date */ + /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */ + buf[5] = bin2bcd(time->tm_mon + 1); + buf[6] = bin2bcd(time->tm_year % 100); + + i2c_smbus_write_i2c_block_data(client, SD2068_SECONDS, 7, buf); + i2c_smbus_write_byte_data(client, SD2068_TIME_ADJ, 0x00); + + sd2068_write_disable(client); + + return 0; +} + +/* + * According to linux specification, only support one-shot alarm + * no periodic alarm mode + */ +static int sd2068_read_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sd2068 *sd2068 = i2c_get_clientdata(client); + unsigned char control, alm_en; + unsigned char ret; + u8 buf[7]; + + mutex_lock(&sd2068->mutex); + + ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); + if (ret < 0) + goto out; + control = ret; + alarm->enabled = (control & SD2068_CTR2_INTAE) ? 1 : 0; + + ret = i2c_smbus_read_i2c_block_data(client, SD2068_ALARM, 7, buf); + if (ret < 0) + goto out; + + ret = i2c_smbus_read_byte_data(client, SD2068_ALARM_EN); + if (ret < 0) + goto out; + alm_en = ret; + /* decode the alarm enable field */ + if (alm_en & SD2068_ALARM_EAS) + alarm->time.tm_sec = bcd2bin(buf[0] & 0x7F); + else + alarm->time.tm_sec = -1; + + if (alm_en & SD2068_ALARM_EAMN) + alarm->time.tm_min = bcd2bin(buf[1] & 0x7F); + else + alarm->time.tm_min = -1; + + if (alm_en & SD2068_ALARM_EAH) + alarm->time.tm_hour = bcd2bin(buf[2] & 0x3F); + else + alarm->time.tm_hour = -1; + + if (alm_en & SD2068_ALARM_EAW) + alarm->time.tm_wday = bcd2bin(buf[3] & 0x7F); + else + alarm->time.tm_wday = -1; + + if (alm_en & SD2068_ALARM_EAD) + alarm->time.tm_mday = bcd2bin(buf[4] & 0x3F); + else + alarm->time.tm_mday = -1; + + if (alm_en & SD2068_ALARM_EAMO) + alarm->time.tm_mon = bcd2bin(buf[5] & 0x1F); + else + alarm->time.tm_mon = -1; + + if (alm_en & SD2068_ALARM_EAY) + alarm->time.tm_year = bcd2bin(buf[6]); + else + alarm->time.tm_year = -1; + + ret = 0; +out: + mutex_unlock(&sd2068->mutex); + return ret; +} + +/* + * linux rtc-module does not support wday alarm + * and only 24h time mode supported indeed + */ +static int sd2068_set_alarm(struct device *dev, struct rtc_wkalrm *alarm) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sd2068 *sd2068 = i2c_get_clientdata(client); + int control; + int ret; + u8 buf[7]; + + if (client->irq <= 0) + return -EINVAL; + + mutex_lock(&sd2068->mutex); + + sd2068_write_enable(client); + + buf[0] = bin2bcd(alarm->time.tm_sec); + buf[1] = bin2bcd(alarm->time.tm_min); + buf[2] = bin2bcd(alarm->time.tm_hour); + buf[3] = bin2bcd(alarm->time.tm_wday); + buf[4] = bin2bcd(alarm->time.tm_mday); + buf[5] = bin2bcd(alarm->time.tm_mon); + buf[6] = bin2bcd(alarm->time.tm_year); + + /* clear alarm interrupt enable bit */ + ret = i2c_smbus_read_byte_data(client, SD2068_CTR2); + if (ret < 0) + goto out; + control = ret; + control &= ~(SD2068_CTR2_INTAE); + ret = i2c_smbus_write_byte_data(client, SD2068_CTR2, control); + if (ret < 0) + goto out; + + ret = i2c_smbus_write_i2c_block_data(client, SD2068_ALARM, 7, buf); + + ret = i2c_smbus_write_byte_data(client, SD2068_ALARM_EN, 0x7f); + + if (alarm->enabled) { + control |= SD2068_CTR2_INTAE; + ret = i2c_smbus_write_byte_data(client, SD2068_CTR2, control); + } +out: + sd2068_write_disable(client); + mutex_unlock(&sd2068->mutex); + return ret; +} + +static int sd2068_alarm_irq_enable(struct device *dev, unsigned int enabled) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sd2068 *sd2068 = i2c_get_clientdata(client); + unsigned char control; + + pr_debug("%s: aie=%d\n", __func__, enabled); + + if (client->irq <= 0) + return -EINVAL; + + sd2068_write_enable(client); + + control = i2c_smbus_read_byte_data(client, SD2068_CTR2); + + if (enabled) { + sd2068->rtc->irq_data |= RTC_AF; + control |= SD2068_CTR2_INTAE; + i2c_smbus_write_byte_data(client, SD2068_CTR2, control); + } else { + sd2068->rtc->irq_data &= ~RTC_AF; + control &= ~SD2068_CTR2_INTAE; + i2c_smbus_write_byte_data(client, SD2068_CTR2, control); + } + + sd2068_write_disable(client); + + return 0; +} + +static irqreturn_t sd2068_irq(int irq, void *dev_id) +{ + struct i2c_client *client = dev_id; + struct sd2068 *sd2068 = i2c_get_clientdata(client); + + disable_irq_nosync(irq); + schedule_work(&sd2068->work); + return IRQ_HANDLED; +} + +static void sd2068_work(struct work_struct *work) +{ + struct sd2068 *sd2068 = container_of(work, struct sd2068, work); + struct i2c_client *client = sd2068->client; + + mutex_lock(&sd2068->mutex); + + rtc_update_irq(sd2068->rtc, 1, RTC_AF | RTC_IRQF); + + if (!sd2068->exiting) + enable_irq(client->irq); + + mutex_unlock(&sd2068->mutex); +} + +static const struct rtc_class_ops sd2068_rtc_ops = { + .read_time = sd2068_read_time, + .set_time = sd2068_set_time, + .read_alarm = sd2068_read_alarm, + .set_alarm = sd2068_set_alarm, + .alarm_irq_enable = sd2068_alarm_irq_enable, +}; + +static int sd2068_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct sd2068 *sd2068; + struct rtc_time rtc_tm; + int ret; + + sd2068 = kzalloc(sizeof(struct sd2068), GFP_KERNEL); + if (!sd2068) + return -ENOMEM; + + sd2068->client = client; + i2c_set_clientdata(client, sd2068); + + INIT_WORK(&sd2068->work, sd2068_work); + mutex_init(&sd2068->mutex); + + sd2068->rtc = rtc_device_register(client->name, &client->dev, + &sd2068_rtc_ops, THIS_MODULE); + if (IS_ERR(sd2068->rtc)) { + ret = PTR_ERR(sd2068->rtc); + dev_err(&client->dev, "unable to register the class device\n"); + goto out_irq; + } + + if (client->irq >= 0) { + ret = request_irq(client->irq, sd2068_irq, 0, + "sd2068", client); + if (ret) { + dev_err(&client->dev, "unable to request IRQ\n"); + goto out_free; + } + } + + sd2068_hw_init(client); + + /* Check RTC Time */ + sd2068_read_time(&client->dev, &rtc_tm); + + if (rtc_valid_tm(&rtc_tm)) { + rtc_tm.tm_year = 100; + rtc_tm.tm_mon = 0; + rtc_tm.tm_mday = 1; + rtc_tm.tm_hour = 0; + rtc_tm.tm_min = 0; + rtc_tm.tm_sec = 0; + + sd2068_set_time(&client->dev, &rtc_tm); + + dev_warn(&client->dev, "warning: invalid RTC value so initializing it\n"); + } + + return 0; + +out_irq: + if (client->irq >= 0) + free_irq(client->irq, client); + +out_free: + kfree(sd2068); + return ret; +} + +static int sd2068_remove(struct i2c_client *client) +{ + struct sd2068 *sd2068 = i2c_get_clientdata(client); + + if (client->irq >= 0) { + mutex_lock(&sd2068->mutex); + sd2068->exiting = 1; + mutex_unlock(&sd2068->mutex); + + free_irq(client->irq, client); + cancel_work_sync(&sd2068->work); + } + + rtc_device_unregister(sd2068->rtc); + kfree(sd2068); + return 0; +} + +static const struct i2c_device_id sd2068_id[] = { + { "sd2068", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, sd2068_id); + +static struct i2c_driver sd2068_driver = { + .driver = { + .name = "rtc-sd2068", + .owner = THIS_MODULE, + }, + .probe = sd2068_probe, + .remove = sd2068_remove, + .id_table = sd2068_id, +}; + +static int __init sd2068_init(void) +{ + return i2c_add_driver(&sd2068_driver); +} + +static void __exit sd2068_exit(void) +{ + i2c_del_driver(&sd2068_driver); +} + +module_init(sd2068_init); +module_exit(sd2068_exit); + +MODULE_AUTHOR("Loongson-gz "); +MODULE_DESCRIPTION("Whwave SD2068 RTC Driver"); +MODULE_LICENSE("GPL");