OpenWrt – Rev 4
?pathlinks?
/*
* RTC client/driver for the Whwave SD2068 Real-Time Clock over I2C
*
* Copyright (C) 2013 Tang, Haifeng <tanghaifeng-gz@loongson.cn>.
*
* 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 <linux/kernel.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/i2c.h>
#include <linux/rtc.h>
#include <linux/bcd.h>
#include <linux/workqueue.h>
#include <linux/slab.h>
#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 <tanghaifeng-gz@loongson.cn>");
MODULE_DESCRIPTION("Whwave SD2068 RTC Driver");
MODULE_LICENSE("GPL");