Skip to content

Commit

Permalink
rtc: palmas: Add RTC driver Palmas series PMIC
Browse files Browse the repository at this point in the history
TI Palmas series PMIC support the RTC and alarm functionality.
Add RTC driver with alarm support for this device.

Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Signed-off-by: Samuel Ortiz <sameo@linux.intel.com>
  • Loading branch information
Laxman Dewangan authored and Samuel Ortiz committed Feb 13, 2013
1 parent 3d50a27 commit 0101e53
Show file tree
Hide file tree
Showing 3 changed files with 350 additions and 0 deletions.
10 changes: 10 additions & 0 deletions drivers/rtc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,16 @@ config RTC_DRV_X1205
This driver can also be built as a module. If so, the module
will be called rtc-x1205.

config RTC_DRV_PALMAS
tristate "TI Palmas RTC driver"
depends on MFD_PALMAS
help
If you say yes here you get support for the RTC of TI PALMA series PMIC
chips.

This driver can also be built as a module. If so, the module
will be called rtc-palma.

config RTC_DRV_PCF8523
tristate "NXP PCF8523"
help
Expand Down
1 change: 1 addition & 0 deletions drivers/rtc/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o
obj-$(CONFIG_RTC_DRV_MV) += rtc-mv.o
obj-$(CONFIG_RTC_DRV_NUC900) += rtc-nuc900.o
obj-$(CONFIG_RTC_DRV_OMAP) += rtc-omap.o
obj-$(CONFIG_RTC_DRV_PALMAS) += rtc-palmas.o
obj-$(CONFIG_RTC_DRV_PCAP) += rtc-pcap.o
obj-$(CONFIG_RTC_DRV_PCF8523) += rtc-pcf8523.o
obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
Expand Down
339 changes: 339 additions & 0 deletions drivers/rtc/rtc-palmas.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,339 @@
/*
* rtc-palmas.c -- Palmas Real Time Clock driver.
* RTC driver for TI Palma series devices like TPS65913,
* TPS65914 power management IC.
*
* Copyright (c) 2012, NVIDIA Corporation.
*
* Author: Laxman Dewangan <ldewangan@nvidia.com>
*
* 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 version 2.
*
* This program is distributed "as is" WITHOUT ANY WARRANTY of any kind,
* whether express or implied; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307, USA
*/

#include <linux/bcd.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/palmas.h>
#include <linux/module.h>
#include <linux/rtc.h>
#include <linux/types.h>
#include <linux/platform_device.h>
#include <linux/pm.h>

struct palmas_rtc {
struct rtc_device *rtc;
struct device *dev;
unsigned int irq;
};

/* Total number of RTC registers needed to set time*/
#define PALMAS_NUM_TIME_REGS (PALMAS_YEARS_REG - PALMAS_SECONDS_REG + 1)

static int palmas_rtc_read_time(struct device *dev, struct rtc_time *tm)
{
unsigned char rtc_data[PALMAS_NUM_TIME_REGS];
struct palmas *palmas = dev_get_drvdata(dev->parent);
int ret;

/* Copy RTC counting registers to static registers or latches */
ret = palmas_update_bits(palmas, PALMAS_RTC_BASE, PALMAS_RTC_CTRL_REG,
PALMAS_RTC_CTRL_REG_GET_TIME, PALMAS_RTC_CTRL_REG_GET_TIME);
if (ret < 0) {
dev_err(dev, "RTC CTRL reg update failed, err: %d\n", ret);
return ret;
}

ret = palmas_bulk_read(palmas, PALMAS_RTC_BASE, PALMAS_SECONDS_REG,
rtc_data, PALMAS_NUM_TIME_REGS);
if (ret < 0) {
dev_err(dev, "RTC_SECONDS reg read failed, err = %d\n", ret);
return ret;
}

tm->tm_sec = bcd2bin(rtc_data[0]);
tm->tm_min = bcd2bin(rtc_data[1]);
tm->tm_hour = bcd2bin(rtc_data[2]);
tm->tm_mday = bcd2bin(rtc_data[3]);
tm->tm_mon = bcd2bin(rtc_data[4]) - 1;
tm->tm_year = bcd2bin(rtc_data[5]) + 100;

return ret;
}

static int palmas_rtc_set_time(struct device *dev, struct rtc_time *tm)
{
unsigned char rtc_data[PALMAS_NUM_TIME_REGS];
struct palmas *palmas = dev_get_drvdata(dev->parent);
int ret;

rtc_data[0] = bin2bcd(tm->tm_sec);
rtc_data[1] = bin2bcd(tm->tm_min);
rtc_data[2] = bin2bcd(tm->tm_hour);
rtc_data[3] = bin2bcd(tm->tm_mday);
rtc_data[4] = bin2bcd(tm->tm_mon + 1);
rtc_data[5] = bin2bcd(tm->tm_year - 100);

/* Stop RTC while updating the RTC time registers */
ret = palmas_update_bits(palmas, PALMAS_RTC_BASE, PALMAS_RTC_CTRL_REG,
PALMAS_RTC_CTRL_REG_STOP_RTC, 0);
if (ret < 0) {
dev_err(dev, "RTC stop failed, err = %d\n", ret);
return ret;
}

ret = palmas_bulk_write(palmas, PALMAS_RTC_BASE, PALMAS_SECONDS_REG,
rtc_data, PALMAS_NUM_TIME_REGS);
if (ret < 0) {
dev_err(dev, "RTC_SECONDS reg write failed, err = %d\n", ret);
return ret;
}

/* Start back RTC */
ret = palmas_update_bits(palmas, PALMAS_RTC_BASE, PALMAS_RTC_CTRL_REG,
PALMAS_RTC_CTRL_REG_STOP_RTC, PALMAS_RTC_CTRL_REG_STOP_RTC);
if (ret < 0)
dev_err(dev, "RTC start failed, err = %d\n", ret);
return ret;
}

static int palmas_rtc_alarm_irq_enable(struct device *dev, unsigned enabled)
{
struct palmas *palmas = dev_get_drvdata(dev->parent);
u8 val;

val = enabled ? PALMAS_RTC_INTERRUPTS_REG_IT_ALARM : 0;
return palmas_write(palmas, PALMAS_RTC_BASE,
PALMAS_RTC_INTERRUPTS_REG, val);
}

static int palmas_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
unsigned char alarm_data[PALMAS_NUM_TIME_REGS];
u32 int_val;
struct palmas *palmas = dev_get_drvdata(dev->parent);
int ret;

ret = palmas_bulk_read(palmas, PALMAS_RTC_BASE,
PALMAS_ALARM_SECONDS_REG,
alarm_data, PALMAS_NUM_TIME_REGS);
if (ret < 0) {
dev_err(dev, "RTC_ALARM_SECONDS read failed, err = %d\n", ret);
return ret;
}

alm->time.tm_sec = bcd2bin(alarm_data[0]);
alm->time.tm_min = bcd2bin(alarm_data[1]);
alm->time.tm_hour = bcd2bin(alarm_data[2]);
alm->time.tm_mday = bcd2bin(alarm_data[3]);
alm->time.tm_mon = bcd2bin(alarm_data[4]) - 1;
alm->time.tm_year = bcd2bin(alarm_data[5]) + 100;

ret = palmas_read(palmas, PALMAS_RTC_BASE, PALMAS_RTC_INTERRUPTS_REG,
&int_val);
if (ret < 0) {
dev_err(dev, "RTC_INTERRUPTS reg read failed, err = %d\n", ret);
return ret;
}

if (int_val & PALMAS_RTC_INTERRUPTS_REG_IT_ALARM)
alm->enabled = 1;
return ret;
}

static int palmas_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
{
unsigned char alarm_data[PALMAS_NUM_TIME_REGS];
struct palmas *palmas = dev_get_drvdata(dev->parent);
int ret;

ret = palmas_rtc_alarm_irq_enable(dev, 0);
if (ret < 0) {
dev_err(dev, "Disable RTC alarm failed\n");
return ret;
}

alarm_data[0] = bin2bcd(alm->time.tm_sec);
alarm_data[1] = bin2bcd(alm->time.tm_min);
alarm_data[2] = bin2bcd(alm->time.tm_hour);
alarm_data[3] = bin2bcd(alm->time.tm_mday);
alarm_data[4] = bin2bcd(alm->time.tm_mon + 1);
alarm_data[5] = bin2bcd(alm->time.tm_year - 100);

ret = palmas_bulk_write(palmas, PALMAS_RTC_BASE,
PALMAS_ALARM_SECONDS_REG, alarm_data, PALMAS_NUM_TIME_REGS);
if (ret < 0) {
dev_err(dev, "ALARM_SECONDS_REG write failed, err = %d\n", ret);
return ret;
}

if (alm->enabled)
ret = palmas_rtc_alarm_irq_enable(dev, 1);
return ret;
}

static int palmas_clear_interrupts(struct device *dev)
{
struct palmas *palmas = dev_get_drvdata(dev->parent);
unsigned int rtc_reg;
int ret;

ret = palmas_read(palmas, PALMAS_RTC_BASE, PALMAS_RTC_STATUS_REG,
&rtc_reg);
if (ret < 0) {
dev_err(dev, "RTC_STATUS read failed, err = %d\n", ret);
return ret;
}

ret = palmas_write(palmas, PALMAS_RTC_BASE, PALMAS_RTC_STATUS_REG,
rtc_reg);
if (ret < 0) {
dev_err(dev, "RTC_STATUS write failed, err = %d\n", ret);
return ret;
}
return 0;
}

static irqreturn_t palmas_rtc_interrupt(int irq, void *context)
{
struct palmas_rtc *palmas_rtc = context;
struct device *dev = palmas_rtc->dev;
int ret;

ret = palmas_clear_interrupts(dev);
if (ret < 0) {
dev_err(dev, "RTC interrupt clear failed, err = %d\n", ret);
return IRQ_NONE;
}

rtc_update_irq(palmas_rtc->rtc, 1, RTC_IRQF | RTC_AF);
return IRQ_HANDLED;
}

static struct rtc_class_ops palmas_rtc_ops = {
.read_time = palmas_rtc_read_time,
.set_time = palmas_rtc_set_time,
.read_alarm = palmas_rtc_read_alarm,
.set_alarm = palmas_rtc_set_alarm,
.alarm_irq_enable = palmas_rtc_alarm_irq_enable,
};

static int palmas_rtc_probe(struct platform_device *pdev)
{
struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
struct palmas_rtc *palmas_rtc = NULL;
int ret;

palmas_rtc = devm_kzalloc(&pdev->dev, sizeof(struct palmas_rtc),
GFP_KERNEL);
if (!palmas_rtc)
return -ENOMEM;

/* Clear pending interrupts */
ret = palmas_clear_interrupts(&pdev->dev);
if (ret < 0) {
dev_err(&pdev->dev, "clear RTC int failed, err = %d\n", ret);
return ret;
}

palmas_rtc->dev = &pdev->dev;
platform_set_drvdata(pdev, palmas_rtc);

/* Start RTC */
ret = palmas_update_bits(palmas, PALMAS_RTC_BASE, PALMAS_RTC_CTRL_REG,
PALMAS_RTC_CTRL_REG_STOP_RTC,
PALMAS_RTC_CTRL_REG_STOP_RTC);
if (ret < 0) {
dev_err(&pdev->dev, "RTC_CTRL write failed, err = %d\n", ret);
return ret;
}

palmas_rtc->irq = platform_get_irq(pdev, 0);

palmas_rtc->rtc = rtc_device_register(pdev->name, &pdev->dev,
&palmas_rtc_ops, THIS_MODULE);
if (IS_ERR(palmas_rtc->rtc)) {
ret = PTR_ERR(palmas_rtc->rtc);
dev_err(&pdev->dev, "RTC register failed, err = %d\n", ret);
return ret;
}

ret = request_threaded_irq(palmas_rtc->irq, NULL,
palmas_rtc_interrupt,
IRQF_TRIGGER_LOW | IRQF_ONESHOT |
IRQF_EARLY_RESUME,
dev_name(&pdev->dev), palmas_rtc);
if (ret < 0) {
dev_err(&pdev->dev, "IRQ request failed, err = %d\n", ret);
rtc_device_unregister(palmas_rtc->rtc);
return ret;
}

device_set_wakeup_capable(&pdev->dev, 1);
return 0;
}

static int palmas_rtc_remove(struct platform_device *pdev)
{
struct palmas_rtc *palmas_rtc = platform_get_drvdata(pdev);

palmas_rtc_alarm_irq_enable(&pdev->dev, 0);
free_irq(palmas_rtc->irq, palmas_rtc);
rtc_device_unregister(palmas_rtc->rtc);
return 0;
}

#ifdef CONFIG_PM_SLEEP
static int palmas_rtc_suspend(struct device *dev)
{
struct palmas_rtc *palmas_rtc = dev_get_drvdata(dev);

if (device_may_wakeup(dev))
enable_irq_wake(palmas_rtc->irq);
return 0;
}

static int palmas_rtc_resume(struct device *dev)
{
struct palmas_rtc *palmas_rtc = dev_get_drvdata(dev);

if (device_may_wakeup(dev))
disable_irq_wake(palmas_rtc->irq);
return 0;
}
#endif

static const struct dev_pm_ops palmas_rtc_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(palmas_rtc_suspend, palmas_rtc_resume)
};

static struct platform_driver palmas_rtc_driver = {
.probe = palmas_rtc_probe,
.remove = palmas_rtc_remove,
.driver = {
.owner = THIS_MODULE,
.name = "palmas-rtc",
.pm = &palmas_rtc_pm_ops,
},
};

module_platform_driver(palmas_rtc_driver);

MODULE_ALIAS("platform:palmas_rtc");
MODULE_DESCRIPTION("TI PALMAS series RTC driver");
MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
MODULE_LICENSE("GPL v2");

0 comments on commit 0101e53

Please sign in to comment.