oleavr-rgl-a500-mini-linux-.../drivers/rtc/rtc-ac200.c
Ole André Vadla Ravnås 169c65d57e Initial commit
2022-05-07 01:01:45 +02:00

865 lines
23 KiB
C
Executable file

/*
* drivers/rtc/rtc-ac200.c
*
* Copyright(c) 2013-2015 Allwinnertech Co., Ltd.
* http://www.allwinnertech.com
*
* Author: huangxin <huangxin@allwinnertech.com>
*
* rtc driver for sunxi external rtc chip
*
* 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.
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/bcd.h>
#include <linux/rtc.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/mfd/acx00-mfd.h>
#include <linux/pinctrl/pinconf-sunxi.h>
#include <linux/pinctrl/consumer.h>
#define DRV_VERSION "0.4.3"
static int ac200_used = 0;
static int ac100_used = 0;
/* reg off defination */
#define LOSC_CTRL_REG0 0xa000
#define LOSC_CTRL_REG1 0xa002
#define LOSC_AUTO_SWT_STA_REG 0xa004
#define INTOSC_CLK_PRESCAL_REG 0xa008
#define RTC_YY_MM_DD_REG0 0xa010
#define RTC_YY_MM_DD_REG1 0xa012
#define RTC_HH_MM_SS_REG0 0xa014
#define RTC_HH_MM_SS_REG1 0xa016
#define ALARM0_COUNTER_REG0 0xa020
#define ALARM0_COUNTER_REG1 0xa022
#define ALARM0_CUR_VLU_REG0 0xa024
#define ALARM0_CUR_VLU_REG1 0xa026
#define ALARM0_ENABLE_REG 0xa028
#define ALARM0_IRQ_EN 0xa02c
#define ALARM0_IRQ_STA_REG 0xa030
#define ALARM_CONFIG_REG 0xa050
#define LOSC_OUT_GATING_REG 0xa060
#define GP_DATA_REG(x) (0xa100 + ((x) << 1)) /* x: 0 ~ 15 */
#define RTC_DEB_REG 0xa170
#define GPL_HOLD_OUTPUT_REG 0xa180
#define VDD_RTC_REG 0xa190
#define IC_CHARA_REG0 0xa1f0
#define IC_CHARA_REG1 0xa1f2
/*LOSC_CTRL_REG0:0xa000*/
#define LOSC_AUTO_SWT_EN 14
#define EXT_LOSC_GSM 2
#define LOSC_SRC_SEL 0
/*LOSC_CTRL_REG1:0xa002*/
#define KEY_FIELD 0
/*LOSC_AUTO_SWT_STA_REG:0xa004*/
#define LOSC_AUTO_SWT_PEND 1
/*RTC_YY_MM_DD_REG0:0xa010*/
#define MONTH 8
#define DAY 0
/*RTC_YY_MM_DD_REG1:0xa012*/
#define LEAP 6
#define YEAR 0
/*RTC_HH_MM_SS_REG0:0xa014*/
#define MINUTE 8
#define SECOND 0
/*RTC_HH_MM_SS_REG1:0xa016*/
#define WK_NO 13
#define HOUR 0
/*ALARM0_COUNTER_REG0:0xa020*/
#define ALARM0_COUNTER_LOW 0
/*ALARM0_COUNTER_REG1:0xa022*/
#define ALARM0_COUNTER_HIGH 0
/*ALARM0_CUR_VLU_REG0:0xa024*/
#define ALARM0_CUR_ULU_LOW 0
/*ALARM0_CUR_VLU_REG1:0xa026*/
#define ALARM0_CUR_VLU_HIGH 0
/*ALARM0_ENABLE_REG:0xa028*/
#define ALM_0_EN 0
/*ALARM0_IRQ_EN:0xa02c*/
//#define ALARM0_IRQ_EN 0
/*ALARM0_IRQ_STA_REG:0xa030*/
#define ALARM0_IRQ_PEND 0
/*ALARM_CONFIG_REG:0xa050*/
#define ALARM_WAKEUP 0
/*LOSC_OUT_GATING_REG:0xa060*/
#define LOSC_OUT_GATING 0
#define RTC_NAME "rtc0_ac200"
#define ENABLE_ALARM
#ifdef ENABLE_ALARM
#define ALARM_IRQ_GPIO GPIOE(0) /* may need change */
#define ALARM_IRQ_NAME "PE0_EINT"
static int rtc_alarmno = 0;
#endif
#define MAX_YEAR_REG_VAL 63 /* year reg max value, it's 63 from ac200 spec */
#define YEAR_SUPPORT_MIN 1970 /* the mimimum year support to set, 1970 is from rtc_valid_tm(rtc-lib.c), donot change */
#define YEAR_SUPPORT_MAX (YEAR_SUPPORT_MIN + MAX_YEAR_REG_VAL) /* the max year we support to set */
#define IS_LEAP_YEAR(year) (((year)%400)==0 || (((year)%100)!=0 && ((year)%4)==0))
#ifdef CONFIG_ARCH_SUN8IW6P1
static script_item_u item_eint;
#endif
struct sunxi_rtc {
int virq; /*alarm irq*/
struct rtc_device *rtc;
#ifdef ENABLE_ALARM
struct work_struct work;
#endif
struct mutex mutex;
struct acx00 *acx00;
};
/* reg_val: raw value to be wrote to the reg, bcd convertion is done outside */
static inline void rtc_write_reg(struct acx00 *acx00, int reg, int value)
{
acx00_reg_write(acx00, reg, value);
}
/* return raw value from the reg, then it may do bcd convertion if needed */
static inline int rtc_read_reg(struct acx00 *acx00, int reg)
{
return acx00_reg_read(acx00, reg);
}
static int rtc_hms_read_start(struct acx00 *acx00)
{
int reg_val;
reg_val = rtc_read_reg(acx00, LOSC_CTRL_REG0)|(0x1<<13);
rtc_write_reg(acx00, LOSC_CTRL_REG0, reg_val);
return 0;
}
static int rtc_hms_read_end(struct acx00 *acx00)
{
int reg_val;
reg_val = rtc_read_reg(acx00, LOSC_CTRL_REG0)&~(0x1<<13);
rtc_write_reg(acx00, LOSC_CTRL_REG0, reg_val);
return 0;
}
static int rtc_hms_write_end(struct acx00 *acx00)
{
int reg_val;
reg_val = rtc_read_reg(acx00, LOSC_CTRL_REG0)|(0x1<<12);
rtc_write_reg(acx00, LOSC_CTRL_REG0, reg_val);
return 0;
}
static int rtc_ymd_read_start(struct acx00 *acx00)
{
int reg_val;
reg_val = rtc_read_reg(acx00, LOSC_CTRL_REG0)|(0x1<<5);
rtc_write_reg(acx00, LOSC_CTRL_REG0, reg_val);
return 0;
}
static int rtc_ymd_read_end(struct acx00 *acx00)
{
int reg_val;
reg_val = rtc_read_reg(acx00, LOSC_CTRL_REG0)&~(0x1<<5);
rtc_write_reg(acx00, LOSC_CTRL_REG0, reg_val);
return 0;
}
static int rtc_ymd_write_end(struct acx00 *acx00)
{
int reg_val;
reg_val = rtc_read_reg(acx00, LOSC_CTRL_REG0)|(0x1<<4);
rtc_write_reg(acx00, LOSC_CTRL_REG0, reg_val);
return 0;
}
/*write y m d*/
static int write_rtc_ymd_reg(struct acx00 *acx00, int reg, int value)
{
int reg_val = 0;
rtc_write_reg(acx00, reg, value);
rtc_ymd_write_end(acx00);
return reg_val;
}
/*read y m d*/
static int read_rtc_ymd_reg(struct acx00 *acx00, int reg)
{
int reg_val;
rtc_ymd_read_start(acx00);
reg_val = rtc_read_reg(acx00, reg);
rtc_ymd_read_end(acx00);
return reg_val;
}
/*write h m s*/
static int write_rtc_hms_reg(struct acx00 *acx00, int reg, int value)
{
int reg_val = 0;
rtc_write_reg(acx00, reg, value);
rtc_hms_write_end(acx00);
return reg_val;
}
/*read h m s*/
static int read_rtc_hms_reg(struct acx00 *acx00, int reg)
{
int reg_val;
rtc_hms_read_start(acx00);
reg_val = rtc_read_reg(acx00, reg);
rtc_hms_read_end(acx00);
return reg_val;
}
/**
* get the actual year from year reg
* @actual_year: year to set, eg: 2014.
*
* return the value to be set to year reg.
*/
static int actual_year_to_reg_year(int actual_year)
{
if (actual_year < YEAR_SUPPORT_MIN || actual_year > YEAR_SUPPORT_MAX) {
pr_err("%s(%d) err: we only support (%d ~ %d), but input %d\n", __func__, __LINE__,
YEAR_SUPPORT_MIN, YEAR_SUPPORT_MAX, actual_year);
actual_year = YEAR_SUPPORT_MAX -1;
}
return actual_year - YEAR_SUPPORT_MIN;
}
/**
* get the year reg value from actual year
* @reg_val: value from year reg.
*
* return the actual year, eg 2014.
*/
static int reg_year_to_actual_year(int reg_val)
{
if (reg_val > MAX_YEAR_REG_VAL)
reg_val = MAX_YEAR_REG_VAL;
return reg_val + YEAR_SUPPORT_MIN;
}
static bool time_valid(struct rtc_time *tm)
{
int actual_year, actual_month, leap_year = 0;
int err = 0;
actual_year = tm->tm_year + 1900; /* tm_year is from 1900 in linux */
actual_month = tm->tm_mon + 1; /* month is 1..12 in RTC reg but 0..11 in linux */
leap_year = IS_LEAP_YEAR(actual_year);
if (rtc_valid_tm(tm) < 0) {
pr_err("%s(%d): time(%d-%d-%d %d:%d:%d) invalid.\n", __func__, __LINE__, actual_year,
actual_month, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
return false;
}
/* int tm_year; years from 1900
* int tm_mon; months since january 0-11
* the input para tm->tm_year is the offset related 1900 */
if(actual_year > YEAR_SUPPORT_MAX || actual_year < YEAR_SUPPORT_MIN) {
pr_err("%s(%d) err: rtc only supports (%d~%d) years, but to set is %d\n", __func__, __LINE__,
YEAR_SUPPORT_MIN, YEAR_SUPPORT_MAX, actual_year);
actual_year = YEAR_SUPPORT_MAX -1;
}
switch(actual_month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
if(tm->tm_mday > 31)
err = __LINE__;
if(tm->tm_hour > 24 || tm->tm_min > 59 || tm->tm_sec > 59)
err = __LINE__;
break;
case 4:
case 6:
case 9:
case 11:
if(tm->tm_mday > 30)
err = __LINE__;
if(tm->tm_hour > 24 || tm->tm_min > 59 || tm->tm_sec > 59)
err = __LINE__;
break;
case 2:
if(leap_year) {
if(tm->tm_mday > 29)
err = __LINE__;
if(tm->tm_hour > 24 || tm->tm_min > 59 || tm->tm_sec > 59)
err = __LINE__;
} else {
if(tm->tm_mday > 28)
err = __LINE__;
if(tm->tm_hour > 24 || tm->tm_min > 59 || tm->tm_sec > 59)
err = __LINE__;
}
break;
default:
err = __LINE__;
break;
}
if (err) {
pr_err("%s err, line %d!\n", __func__, err);
return false;
}
return true;
}
static int rtc_settime(struct sunxi_rtc *rtc_dev, struct rtc_time *tm)
{
int actual_year, actual_month, leap_year = 0, reg_val;
unsigned int timeout = 0;
actual_year = tm->tm_year + 1900; /* tm_year is from 1900 in linux */
actual_month = tm->tm_mon + 1; /* month is 1..12 in RTC reg but 0..11 in linux */
leap_year = IS_LEAP_YEAR(actual_year);
pr_debug("%s(%d): time to set %d-%d-%d %d:%d:%d\n", __func__, __LINE__, actual_year,
actual_month, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
/* prevent the application seting the error time */
if (!time_valid(tm)) {
#if 1
pr_err("%s(%d) err: date %d-%d-%d %d:%d:%d invalid!\n", __func__, __LINE__,
actual_year, actual_month, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
return -EINVAL;
#else
pr_info("%s(%d) err: date %d-%d-%d %d:%d:%d invalid, so reset to 1970-1-1 00:00:00\n", __func__, __LINE__,
actual_year, actual_month, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
tm->tm_sec = 0;
tm->tm_min = 0;
tm->tm_hour = 0;
tm->tm_mday = 0;
tm->tm_mon = 0; /* 0 is month 1(January) */
tm->tm_year = 70; /* tm_year=0 is 1900, so tm_year=70 is 1970 */
actual_year = tm->tm_year + 1900; /* refresh actual_year for actual_year_to_reg_year below */
actual_month = tm->tm_mon + 1; /* refresh actual_month for below */
leap_year = IS_LEAP_YEAR(actual_year); /* refresh leap_year flag for below */
#endif
}
/* set year */
reg_val = actual_year_to_reg_year(actual_year);
if (leap_year)
reg_val |= (0x1<<LEAP);
write_rtc_ymd_reg(rtc_dev->acx00, RTC_YY_MM_DD_REG1, reg_val);
write_rtc_ymd_reg(rtc_dev->acx00, RTC_YY_MM_DD_REG0, ((actual_month<<MONTH)|tm->tm_mday));
/* set time */
write_rtc_hms_reg(rtc_dev->acx00, RTC_HH_MM_SS_REG1,((tm->tm_wday<<WK_NO)|tm->tm_hour));
write_rtc_hms_reg(rtc_dev->acx00, RTC_HH_MM_SS_REG0,((tm->tm_min<<MINUTE)|tm->tm_sec));
msleep(30); /* delay 30ms to wait write stable */
timeout = 0xffff;
while((rtc_read_reg(rtc_dev->acx00, LOSC_CTRL_REG0)&((0x3<<9)))&&(--timeout))
if (timeout == 0) {
pr_err("fail to set rtc time:%s,l:%d.\n", __func__, __LINE__);
return -1;
}
timeout = 0xffff;
while((rtc_read_reg(rtc_dev->acx00, LOSC_CTRL_REG0)&(0x3<<7))&&(--timeout))
if (timeout == 0) {
pr_err("fail to set rtc time:%s,l:%d.\n", __func__, __LINE__);
return -1;
}
pr_info("%s(%d): set time %d-%d-%d %d:%d:%d success!\n", __func__, __LINE__, actual_year,
actual_month, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
return 0;
}
static int rtc_gettime(struct sunxi_rtc *rtc_dev, struct rtc_time *tm)
{
struct rtc_time time_tmp;
int reg_val;
/* get sec */
reg_val = read_rtc_hms_reg(rtc_dev->acx00, RTC_HH_MM_SS_REG0);
reg_val &= 0x3f;
time_tmp.tm_sec = reg_val;
/* get min */
reg_val = read_rtc_hms_reg(rtc_dev->acx00, RTC_HH_MM_SS_REG0);
reg_val = reg_val>>MINUTE;
time_tmp.tm_min = reg_val;
/* get hour */
reg_val = read_rtc_hms_reg(rtc_dev->acx00, RTC_HH_MM_SS_REG1);
reg_val &= 0x1f;
time_tmp.tm_hour = reg_val;
/* get week day */
reg_val = read_rtc_hms_reg(rtc_dev->acx00, RTC_HH_MM_SS_REG1);
reg_val = reg_val>>WK_NO;
time_tmp.tm_wday = reg_val;
/* get day */
reg_val = read_rtc_ymd_reg(rtc_dev->acx00, RTC_YY_MM_DD_REG0);
reg_val&=0x1f;
time_tmp.tm_mday = reg_val;
/* get month */
reg_val = read_rtc_ymd_reg(rtc_dev->acx00, RTC_YY_MM_DD_REG0);
reg_val = reg_val>>MONTH;
time_tmp.tm_mon = reg_val;
time_tmp.tm_mon -= 1; /* month is 1..12 in RTC reg but 0..11 in linux */
/* get year */
reg_val = read_rtc_ymd_reg(rtc_dev->acx00, RTC_YY_MM_DD_REG1);
reg_val&=0x3f;
time_tmp.tm_year = reg_val;
time_tmp.tm_year = reg_year_to_actual_year(time_tmp.tm_year) - 1900; /* in linux, tm_year=0 is 1900 */
pr_info("%s(%d): read time %d-%d-%d %d:%d:%d\n", __func__, __LINE__, time_tmp.tm_year + 1900,
time_tmp.tm_mon + 1, time_tmp.tm_mday, time_tmp.tm_hour, time_tmp.tm_min, time_tmp.tm_sec);
/* check if time read from rtc reg is OK */
if (!time_valid(&time_tmp)) {
pr_err("%s(%d) err: retrieved date/time is not valid.\n", __func__, __LINE__);
return -EIO;
}
*tm = time_tmp;
return 0;
}
static int sunxi_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_rtc *rtc_dev = platform_get_drvdata(pdev);
int ret = 0;
ret = rtc_gettime(rtc_dev, tm);
return ret;
}
static int sunxi_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_rtc *rtc_dev = platform_get_drvdata(pdev);
int ret = 0;
mutex_lock(&rtc_dev->mutex);
ret = rtc_settime(rtc_dev, tm);
mutex_unlock(&rtc_dev->mutex);
return ret;
}
#ifdef ENABLE_ALARM
void alarm_enable(struct sunxi_rtc *rtc_dev)
{
int reg_val = 0;
/* enable alarm irq */
reg_val = rtc_read_reg(rtc_dev->acx00, SYS_IRQ_ENABLE);
reg_val |= (0x1<<RTC_IRQ_ENABLE)|(0x1<<INTB_OUTPUT_ENABLE);
rtc_write_reg(rtc_dev->acx00, SYS_IRQ_ENABLE, reg_val);
rtc_write_reg(rtc_dev->acx00, ALARM0_ENABLE_REG, 0x1);
rtc_write_reg(rtc_dev->acx00, ALARM0_IRQ_EN, 0x1);
}
static void alarm_disable(struct sunxi_rtc *rtc_dev)
{
int reg_val = 0;
/* disable alarm irq */
reg_val = rtc_read_reg(rtc_dev->acx00, SYS_IRQ_ENABLE);
reg_val &= ~(0x1<<RTC_IRQ_ENABLE);
reg_val &= ~(0x1<<INTB_OUTPUT_ENABLE);
rtc_write_reg(rtc_dev->acx00, SYS_IRQ_ENABLE, reg_val);
rtc_write_reg(rtc_dev->acx00, ALARM0_ENABLE_REG, 0x0);
rtc_write_reg(rtc_dev->acx00, ALARM0_IRQ_EN, 0x0);
/* clear alarm irq pending */
reg_val = rtc_read_reg(rtc_dev->acx00, SYS_IRQ_STATUS);
if (reg_val & (0x1<<RTC_IRQ_STATUS)) {
pr_err("%s(%d) maybe err: alarm irq pending is set, clear it! reg is 0x%x\n", __func__, __LINE__, reg_val);
reg_val &= ~(0x1<<RTC_IRQ_STATUS);
rtc_write_reg(rtc_dev->acx00, SYS_IRQ_STATUS, reg_val);
}
reg_val = rtc_read_reg(rtc_dev->acx00, ALARM0_IRQ_STA_REG);
if (reg_val & 0x1) {
pr_err("%s(%d) maybe err: alarm irq pending is set, clear it! reg is 0x%x\n", __func__, __LINE__, reg_val);
rtc_write_reg(rtc_dev->acx00, ALARM0_IRQ_STA_REG, 0x0);
}
rtc_write_reg(rtc_dev->acx00, ALARM0_COUNTER_REG0, 0x0);
rtc_write_reg(rtc_dev->acx00, ALARM0_COUNTER_REG1, 0x0);
}
static int sunxi_alarm_enable(struct sunxi_rtc *rtc_dev)
{
mutex_lock(&rtc_dev->mutex);
alarm_enable(rtc_dev);
mutex_unlock(&rtc_dev->mutex);
return 0;
}
static int sunxi_alarm_disable(struct sunxi_rtc *rtc_dev)
{
mutex_lock(&rtc_dev->mutex);
alarm_disable(rtc_dev);
mutex_unlock(&rtc_dev->mutex);
return 0;
}
#ifdef CONFIG_ARCH_SUN8IW6P1
static irqreturn_t sunxi_irq_handle(int irq, void *dev_id)
{
struct sunxi_rtc *rtc_dev = dev_id;
(void)schedule_work(&rtc_dev->work);
return 0;
}
#endif
static void alarm_irq_work(struct work_struct *work)
{
struct sunxi_rtc *rtc_dev = container_of(work, struct sunxi_rtc, work);
int reg_val = 0;
mutex_lock(&rtc_dev->mutex);
/* clear alarm irq pending */
reg_val = rtc_read_reg(rtc_dev->acx00, ALARM0_IRQ_STA_REG);
if (reg_val < 0) {
mutex_unlock(&rtc_dev->mutex);
pr_err("%s(%d) err: read alarm int status reg failed!\n", __func__, __LINE__);
return;
}
if (reg_val & 1) {
rtc_write_reg(rtc_dev->acx00, ALARM0_IRQ_STA_REG, 1);
mutex_unlock(&rtc_dev->mutex);
reg_val = rtc_read_reg(rtc_dev->acx00, SYS_IRQ_STATUS);
if (reg_val & (0x1<<RTC_IRQ_STATUS)) {
reg_val &= ~(0x1<<RTC_IRQ_STATUS);
rtc_write_reg(rtc_dev->acx00, SYS_IRQ_STATUS, reg_val);
}
rtc_update_irq(rtc_dev->rtc, 1, RTC_AF | RTC_IRQF);
return;
}
mutex_unlock(&rtc_dev->mutex);
}
static int sunxi_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alarm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_rtc *rtc_dev = platform_get_drvdata(pdev);
unsigned long alarm_cur = 0, alarm_cnt = 0;
unsigned long alarm_seconds = 0;
int reg_val = 0;
int ret = 0;
mutex_lock(&rtc_dev->mutex);
alarm_cur = rtc_read_reg(rtc_dev->acx00, ALARM0_CUR_VLU_HIGH);
alarm_cur = (alarm_cur<<15)|(rtc_read_reg(rtc_dev->acx00, ALARM0_CUR_ULU_LOW));
alarm_cnt = rtc_read_reg(rtc_dev->acx00, ALARM0_COUNTER_HIGH);
alarm_cnt = (alarm_cnt<<15)|rtc_read_reg(rtc_dev->acx00, ALARM0_COUNTER_HIGH);
dev_err(dev, "alarm_cnt: %lu, alarm_cur: %lu\n", alarm_cnt, alarm_cur);
if (alarm_cur > alarm_cnt) {
/* alarm is disabled. */
alarm->enabled = 0;
alarm->time.tm_mon = -1;
alarm->time.tm_mday = -1;
alarm->time.tm_year = -1;
alarm->time.tm_hour = -1;
alarm->time.tm_min = -1;
alarm->time.tm_sec = -1;
goto end;
}
ret = sunxi_rtc_read_time(dev, &alarm->time);
if (ret) {
ret = -EINVAL;
goto end;
}
rtc_tm_to_time(&alarm->time, &alarm_seconds);
alarm_cnt = (alarm_cnt - alarm_cur);
alarm_cur = 0;
alarm_seconds += alarm_cnt;
rtc_time_to_tm(alarm_seconds, &alarm->time);
dev_dbg(dev, "alarm_seconds: %lu\n", alarm_seconds);
reg_val = rtc_read_reg(rtc_dev->acx00, ALARM0_ENABLE_REG);
if (reg_val & 0x1)
alarm->enabled = 1;
end:
mutex_unlock(&rtc_dev->mutex);
return ret;
}
static int sunxi_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_rtc *rtc_dev = platform_get_drvdata(pdev);
struct rtc_time *tm_set = &alrm->time, tm_now;
unsigned long time_set = 0, time_now = 0;
int ret = 0;
ret = sunxi_rtc_read_time(dev, &tm_now);
pr_info("%s(%d): time to set (%d-%d-%d %d:%d:%d), get cur time (%d-%d-%d %d:%d:%d)\n", __func__, __LINE__,
tm_set->tm_year + 1900, tm_set->tm_mon + 1, tm_set->tm_mday, tm_set->tm_hour, tm_set->tm_min, tm_set->tm_sec,
tm_now.tm_year + 1900, tm_now.tm_mon + 1, tm_now.tm_mday, tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
/* prevent the application seting the error time */
if (!time_valid(tm_set)) {
pr_err("%s(%d) err: time to set is not valid!\n", __func__, __LINE__);
return -EINVAL;
}
/* if alarm date is before now, omit it */
rtc_tm_to_time(tm_set, &time_set);
rtc_tm_to_time(&tm_now, &time_now);
if (!ret && time_set <= time_now) {
pr_err("%s(%d) err: the day has been passed!\n", __func__, __LINE__);
return -EINVAL;
}
mutex_lock(&rtc_dev->mutex);
/* disable alarm */
alarm_disable(rtc_dev);
/* Get gap time, max is 0xFFFFFFFF */
time_set = (time_set - time_now) & 0xFFFFFFFF;
/* Rewrite the alm count */
rtc_write_reg(rtc_dev->acx00, ALARM0_COUNTER_REG0, (time_set&0xFFFF));
rtc_write_reg(rtc_dev->acx00, ALARM0_COUNTER_REG1, (time_set>>15)&0xFFFF);
pr_debug("%s(%d): alarm->enabled=%d\n", __func__, __LINE__, alrm->enabled);
/* enable alarm if flag set */
if (alrm->enabled) {
alarm_enable(rtc_dev);
}
mutex_unlock(&rtc_dev->mutex);
return 0;
}
static int sunxi_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
{
struct platform_device *pdev = to_platform_device(dev);
struct sunxi_rtc *rtc_dev = platform_get_drvdata(pdev);
int ret = 0;
pr_debug("%s(%d): enabled=%d\n", __func__, __LINE__, enabled);
if (!enabled) {
ret = sunxi_alarm_disable(rtc_dev);
if (ret < 0)
pr_err("%s(%d) err, disable alarm failed!\n", __func__, __LINE__);
} else {
ret = sunxi_alarm_enable(rtc_dev);
}
return ret;
}
#endif
static const struct rtc_class_ops sunxi_rtc_ops = {
.read_time = sunxi_rtc_read_time,
.set_time = sunxi_rtc_set_time,
#ifdef ENABLE_ALARM
.read_alarm = sunxi_rtc_getalarm,
.set_alarm = sunxi_rtc_setalarm,
.alarm_irq_enable = sunxi_rtc_alarm_irq_enable,
#endif
};
static int __init sunxi_rtc_probe(struct platform_device *pdev)
{
struct sunxi_rtc *rtc_dev;
int err = 0;
#ifdef CONFIG_ARCH_SUN8IW6P1
int ret = 0;
int reg_val = 0;
#ifdef ENABLE_ALARM
int req_status = 0;
script_item_value_type_e type;
#endif
#endif
rtc_dev = kzalloc(sizeof(struct sunxi_rtc), GFP_KERNEL);
if (!rtc_dev)
return -ENOMEM;
/* must before rtc_device_register, because it will call rtc_device_register -> __rtc_read_alarm ->
* rtc_read_time-> __rtc_read_time -> sunxi_rtc_read_time, witch may use platform_get_drvdata. */
platform_set_drvdata(pdev, rtc_dev);
rtc_dev->acx00 = dev_get_drvdata(pdev->dev.parent);
#ifdef ENABLE_ALARM
INIT_WORK(&rtc_dev->work, alarm_irq_work);
device_init_wakeup(&pdev->dev, true); /* enable alarm wakeup */
#endif
mutex_init(&rtc_dev->mutex);
#ifdef CONFIG_ARCH_SUN8IW6P1
/*
* Step1: select RTC clock source
*/
reg_val = rtc_read_reg(rtc_dev->acx00, LOSC_CTRL_REG1);
reg_val |= (0x16ab<<KEY_FIELD);
rtc_write_reg(rtc_dev->acx00, LOSC_CTRL_REG1, reg_val);
reg_val = rtc_read_reg(rtc_dev->acx00, LOSC_CTRL_REG0);
reg_val |= (0x1<<LOSC_AUTO_SWT_EN);
reg_val |= ((0x1<<LOSC_SRC_SEL) | (0x2<<EXT_LOSC_GSM));
reg_val |= (EXT_LOSC_GSM);
rtc_write_reg(rtc_dev->acx00, LOSC_CTRL_REG0, reg_val);
/*
* Step2: check set result
*/
reg_val = rtc_read_reg(rtc_dev->acx00, LOSC_CTRL_REG0);
if(!(reg_val & (0x1 << LOSC_SRC_SEL))){
printk(KERN_ERR "[RTC] WARNING: Rtc time will be wrong!!LOSC_CTRL_REG0:%x:\n", rtc_read_reg(rtc_dev->acx00, LOSC_CTRL_REG0));
}
#endif
rtc_dev->rtc = rtc_device_register(RTC_NAME, &pdev->dev, &sunxi_rtc_ops, THIS_MODULE);
if (IS_ERR(rtc_dev->rtc)) {
err = PTR_ERR(rtc_dev->rtc);
kfree(rtc_dev);
return err;
}
#if defined ENABLE_ALARM && defined CONFIG_ARCH_SUN8IW6P1
sunxi_alarm_disable(rtc_dev);
type = script_get_item("rtc0", "rtc_int_ctrl", &item_eint);
if (SCIRPT_ITEM_VALUE_TYPE_PIO != type) {
pr_err("[AC100] script_get_item return type err\n");
}
rtc_dev->virq = gpio_to_irq(item_eint.gpio.gpio);
if (IS_ERR_VALUE(rtc_dev->virq)) {
pr_warn("[rtc_dev] map gpio to virq failed, errno = %d\n",rtc_dev->virq);
return -EINVAL;
}
rtc_alarmno = rtc_dev->virq;
pr_debug("[rtc_dev] gpio [%d] map to virq [%d] ok\n", item_eint.gpio.gpio, rtc_dev->virq);
/* request virq, set virq type to high level trigger */
ret = devm_request_irq(&rtc_dev->rtc->dev, rtc_dev->virq, sunxi_irq_handle, IRQF_TRIGGER_FALLING, "ALARM_EINT", rtc_dev);
if (IS_ERR_VALUE(ret)) {
pr_warn("[AC100] request virq %d failed, errno = %d\n", rtc_dev->virq, ret);
return -EINVAL;
}
/*
* item_eint.gpio.gpio = GPIO*(*);
* select HOSC 24Mhz(PIO Interrupt Clock Select)
*/
req_status = gpio_request(item_eint.gpio.gpio, NULL);
if (0 != req_status) {
pr_warn("[AC100]request gpio[%d] failed!\n", item_eint.gpio.gpio);
return -EINVAL;
}
gpio_set_debounce(item_eint.gpio.gpio, 1);
#endif
return 0;
}
static int __exit sunxi_rtc_remove(struct platform_device *pdev)
{
struct rtc_device *rtc_dev = platform_get_drvdata(pdev);
#ifdef ENABLE_ALARM
devm_free_irq(&rtc_dev->dev, rtc_alarmno, NULL);
#endif
rtc_device_unregister(rtc_dev);
kfree(rtc_dev);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver sunxi_rtc_driver = {
.probe = sunxi_rtc_probe,
.remove = __exit_p(sunxi_rtc_remove),
.driver = {
.name = RTC_NAME,
.owner = THIS_MODULE,
},
};
static int __init sunxi_rtc_init(void)
{
if (ac200_used&&(ac100_used==0)) {
return platform_driver_register(&sunxi_rtc_driver);
}
return 0;
}
static void __exit sunxi_rtc_exit(void)
{
platform_driver_unregister(&sunxi_rtc_driver);
}
MODULE_AUTHOR("liugang");
MODULE_DESCRIPTION("Allwinner RTC driver");
MODULE_LICENSE("GPL");
MODULE_VERSION(DRV_VERSION);
module_init(sunxi_rtc_init);
module_exit(sunxi_rtc_exit);