From patchwork Thu Dec 20 07:40:53 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jonghwa Lee X-Patchwork-Id: 207617 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from mail-qa0-f56.google.com (mail-qa0-f56.google.com [209.85.216.56]) (using TLSv1 with cipher ECDHE-RSA-RC4-SHA (128/128 bits)) (Client CN "smtp.gmail.com", Issuer "Google Internet Authority" (not verified)) by ozlabs.org (Postfix) with ESMTPS id 97E002C008E for ; Thu, 20 Dec 2012 18:41:04 +1100 (EST) Received: by mail-qa0-f56.google.com with SMTP id cr7sf1602786qab.1 for ; Wed, 19 Dec 2012 23:41:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=googlegroups.com; s=20120806; h=x-received:mime-version:x-beenthere:x-received:x-received :received-spf:x-auditid:from:to:cc:subject:date:message-id:x-mailer :dlp-filter:x-mtr:x-brightmail-tracker:x-brightmail-tracker :x-cfilter-loop:x-original-sender:x-original-authentication-results :reply-to:precedence:mailing-list:list-id:x-google-group-id :list-post:list-help:list-archive:sender:list-subscribe :list-unsubscribe:content-type; bh=kKcG6hRQD9ecakO0OajGf3h/vlBoBrjzmALPm8y5REA=; b=FJPi7kCELV709+SCt1t4CRQH/5KbldGDZonGcHDWTJ7yy4WWL1rnb/gj9I/DuygCSU OgNgU9vNJuXysYDSk3SS9pqhp/p0O1tdqgCBhzjW3yxEFyTXe20/DeXBTDn5G9aP9K28 SSNiET/G6jJHf2PD+1gj0yIxHR0tW9kuLWziy+CgIt7sUfOL3uGvkjbPDgwTjBFgp7pa /b6s5cCIjzrkeIgL4JLuFjLh4bSjgEuZzA5Fnbm7ELWjNIHgbhNvRIg0hpzepbB3MtY4 4xG4gwSMat2vb2rIvCYcfiDGNRAD/k8AiaS1iqewMw/fAXznVe/1HKdC1nB6Cv/HRoYk 8ciA== X-Received: by 10.50.42.194 with SMTP id q2mr3350499igl.11.1355989261350; Wed, 19 Dec 2012 23:41:01 -0800 (PST) MIME-Version: 1.0 X-BeenThere: rtc-linux@googlegroups.com Received: by 10.50.220.225 with SMTP id pz1ls5107851igc.8.canary; Wed, 19 Dec 2012 23:41:00 -0800 (PST) X-Received: by 10.66.84.100 with SMTP id x4mr1679647pay.43.1355989260684; Wed, 19 Dec 2012 23:41:00 -0800 (PST) X-Received: by 10.66.84.100 with SMTP id x4mr1679646pay.43.1355989260672; Wed, 19 Dec 2012 23:41:00 -0800 (PST) Received: from mailout4.samsung.com (mailout4.samsung.com. [203.254.224.34]) by gmr-mx.google.com with ESMTP id uz6si1530409pbc.0.2012.12.19.23.41.00; Wed, 19 Dec 2012 23:41:00 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of jonghwa3.lee@samsung.com designates 203.254.224.34 as permitted sender) client-ip=203.254.224.34; Received: from epcpsbgm2.samsung.com (epcpsbgm2 [203.254.230.27]) by mailout4.samsung.com (Oracle Communications Messaging Server 7u4-24.01(7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTP id <0MFB00JTAK02B5P0@mailout4.samsung.com> for rtc-linux@googlegroups.com; Thu, 20 Dec 2012 16:40:59 +0900 (KST) Received: from epcpsbgm2.samsung.com ( [203.254.230.44]) by epcpsbgm2.samsung.com (EPCPMTA) with SMTP id 22.36.12699.B01C2D05; Thu, 20 Dec 2012 16:40:59 +0900 (KST) X-AuditID: cbfee61b-b7f616d00000319b-0d-50d2c10b021b Received: from epmmp1.local.host ( [203.254.227.16]) by epcpsbgm2.samsung.com (EPCPMTA) with SMTP id E0.36.12699.A01C2D05; Thu, 20 Dec 2012 16:40:58 +0900 (KST) Received: from localhost.localdomain ([10.90.51.58]) by mmp1.samsung.com (Oracle Communications Messaging Server 7u4-24.01 (7.0.4.24.0) 64bit (built Nov 17 2011)) with ESMTPA id <0MFB000RKK089830@mmp1.samsung.com> for rtc-linux@googlegroups.com; Thu, 20 Dec 2012 16:40:58 +0900 (KST) From: Jonghwa Lee To: linux-kernel@vger.kernel.org Cc: rtc-linux@googlegroups.com, Aleessandro Zummo , a.kesavan@samsung.com, akpm@linux-foundation.org, joe@perches.com, Jonghwa Lee , Chiwoong Byun , Myugnjoo Ham , Kyungmin Park Subject: [rtc-linux] [PATCH v2] RTC: MAX77686: Add Maxim 77686 driver Date: Thu, 20 Dec 2012 16:40:53 +0900 Message-id: <1355989253-27471-1-git-send-email-jonghwa3.lee@samsung.com> X-Mailer: git-send-email 1.7.9.5 DLP-Filter: Pass X-MTR: 20000000000000000@CPGS X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFjrGLMWRmVeSWpSXmKPExsVy+t8zHV3ug5cCDL5u5rPY39nB6MDosWfi SbYAxigum5TUnMyy1CJ9uwSujO2vzjEX7JvJWNF9+hV7A+P5mi5GTg4JAROJFb+PMUHYYhIX 7q1n62Lk4hASWMYoMffKAyaYojXLzrBCJBYxSmyffh6qajGTROuhA8wgVWwCOhL/991kB7FF BBQkNvc+A+tgFtjJJHF03go2kISwgKVE27ttjCA2i4CqxOLjDWA2r4CHxJtzp4EaOIDWKUjM mWQDUSIg8W3yIRaIsKzEJpBVXEDmATaJfd/a2SGuk5Q4uOIGywRGwQWMDKsYRVMLkguKk9Jz jfSKE3OLS/PS9ZLzczcxQgJLegfjqgaLQ4wCHIxKPLyLpl0KEGJNLCuuzD3EKMHBrCTCu/DW xQAh3pTEyqrUovz4otKc1OJDjD5Al0xklhJNzgcGfV5JvKGxgbGhoaWhmamlqQEOYSVx3maP lAAhgfTEktTs1NSC1CKYcUwcnFINjFp1Uwy+Cpg+eb9tOy/r4xNnrz2MYS7tK/+6dd/vM92y Fse9JUMXmKyQiHPOyNzyOGi1cenyw35RnjfUTEpLr7VcdUld/uzop0ZTRWnhjffWXjKbqvjb urVIXFshvW/5yQMLtryOvzCrjuHmUf4tx9dwtH1NXBH0Wj33SLHpK7dJ7NfOSMd0RCixFGck GmoxFxUnAgC3rgjEWQIAAA== X-Brightmail-Tracker: H4sIAAAAAAAAA+NgFvrKLMWRmVeSWpSXmKPExsVy+t9jAV2ug5cCDM7et7bY39nB6MDosWfi SbYAxqgGRpuM1MSU1CKF1Lzk/JTMvHRbJe/geOd4UzMDQ11DSwtzJYW8xNxUWyUXnwBdt8wc oLFKCmWJOaVAoYDE4mIlfTtME0JD3HQtYBojdH1DguB6jAzQQMI6xoztr84xF+ybyVjRffoV ewPj+ZouRk4OCQETiTXLzrBC2GISF+6tZ+ti5OIQEljEKLF9+nkoZzGTROuhA8wgVWwCOhL/ 991kB7FFBBQkNvc+YwUpYhbYySRxdN4KNpCEsIClRNu7bYwgNouAqsTi4w1gNq+Ah8Sbc6eB GjiA1ilIzJlkM4GRewEjwypG0dSC5ILipPRcI73ixNzi0rx0veT83E2M4LB9Jr2DcVWDxSFG AQ5GJR7eRdMuBQixJpYVV+YeYpTgYFYS4V1462KAEG9KYmVValF+fFFpTmrxIUYfoOUTmaVE k/OBMZVXEm9obGJmZGlkZmxibmyMQ1hJnLfZIyVASCA9sSQ1OzW1ILUIZhwTB6dUA2PcIsdT b+I9Z/HtKP8ZoFpjqyDy7NCNoM8uoR/UdtWrO3Y+nB6dM1PGd/KaH6HTDd1rFnVdeeSoYpmd 9N1fy/3mqQ3rf5gq9XVmnfIzNfU2Cdl+W+jKJB7PKzO54zkP285YOO9Q1e6P67y2T54z5/7/ ndNvrdSrSS1OUX3JELhCbfPll0uq9vArsRRnJBpqMRcVJwIA6frtaogCAAA= X-CFilter-Loop: Reflected X-Original-Sender: jonghwa3.lee@samsung.com X-Original-Authentication-Results: gmr-mx.google.com; spf=pass (google.com: best guess record for domain of jonghwa3.lee@samsung.com designates 203.254.224.34 as permitted sender) smtp.mail=jonghwa3.lee@samsung.com Reply-To: rtc-linux@googlegroups.com Precedence: list Mailing-list: list rtc-linux@googlegroups.com; contact rtc-linux+owners@googlegroups.com List-ID: X-Google-Group-Id: 712029733259 List-Post: , List-Help: , List-Archive: Sender: rtc-linux@googlegroups.com List-Subscribe: , List-Unsubscribe: , Add driver for support max77686 rtc. MAX77686 rtc support smpl and wtsr mode. It has two alarm register which can be used for alarming to wake system up. This drvier uses regmap to access its register. Signed-off-by: Chiwoong Byun Signed-off-by: Jonghwa Lee Signed-off-by: Myugnjoo Ham Signed-off-by: Kyungmin Park --- v2 Code cleaning referring to Andrew's comments. - Add MAX77686's WTSR, SMPL kernel configuration options. - Remove __devinit, __devexit, __devexit_p , since COFIG_HOTPLUG doesn't exixted anymore. - Swap max77686_rtc_calculate_wday() function with fls(). - Remove usless inline definition. drivers/rtc/Kconfig | 30 +++ drivers/rtc/Makefile | 1 + drivers/rtc/rtc-max77686.c | 642 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 673 insertions(+) create mode 100644 drivers/rtc/rtc-max77686.c diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 19c03ab..94da7ec 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -233,6 +233,36 @@ config RTC_DRV_MAX8998 This driver can also be built as a module. If so, the module will be called rtc-max8998. +config RTC_DRV_MAX77686 + tristate "Maxim MAX77686" + depends on MFD_MAX77686 + help + If you say yes here you will get support for the + RTC of Maxim MAX77686 PMIC. + + This driver can also be built as a module. If so, the module + will be called rtc-max77686. + +config MAX77686_RTC_WTSR + bool "MAX77686_RTC_WTSR" + depends on RTC_DRV_MAX77686 + help + MAX77686 rtc can supports WTSR(Watchdog Timeout and Software Reset). + WTSR event would keep the PMIC on regardless of PWRHOLD signal. + When PWRHOLD signal is falling, it turns on WTSR timer rather executes + reset imidiately. After timer expiring, it determine to reset the PMIC + depends on PWRHOLD state. + +config MAX77686_RTC_SMPL + bool "MAX77686_RTC_SMPL" + depends on RTC_DRV_MAX77686 + help + MAX77686 rtc can supports SMPL(Sudden Momentary Power Loss). + SMPL function can be used to intiate a power up sequence after momentarily + loosing contact to the battery pack. If battery goes under UVLO(Under + Lock Out) it starts SMPL timer. After timer exipiring, it checks battery + whether its state recovers from UVLO or not to make power supplies keep on. + config RTC_DRV_RS5C372 tristate "Ricoh R2025S/D, RS5C372A/B, RV5C386, RV5C387A" help diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 56297f0..3cc94c9 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -69,6 +69,7 @@ obj-$(CONFIG_RTC_DRV_MAX8907) += rtc-max8907.o obj-$(CONFIG_RTC_DRV_MAX8925) += rtc-max8925.o obj-$(CONFIG_RTC_DRV_MAX8998) += rtc-max8998.o obj-$(CONFIG_RTC_DRV_MAX6902) += rtc-max6902.o +obj-$(CONFIG_RTC_DRV_MAX77686) += rtc-max77686.o obj-$(CONFIG_RTC_DRV_MC13XXX) += rtc-mc13xxx.o obj-$(CONFIG_RTC_DRV_MSM6242) += rtc-msm6242.o obj-$(CONFIG_RTC_DRV_MPC5121) += rtc-mpc5121.o diff --git a/drivers/rtc/rtc-max77686.c b/drivers/rtc/rtc-max77686.c new file mode 100644 index 0000000..51f5365 --- /dev/null +++ b/drivers/rtc/rtc-max77686.c @@ -0,0 +1,642 @@ +/* + * RTC driver for Maxim MAX77686 + * + * Copyright (C) 2012 Samsung Electronics Co.Ltd + * + * based on rtc-max8997.c + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* RTC Control Register */ +#define BCD_EN_SHIFT 0 +#define BCD_EN_MASK (1 << BCD_EN_SHIFT) +#define MODEL24_SHIFT 1 +#define MODEL24_MASK (1 << MODEL24_SHIFT) +/* RTC Update Register1 */ +#define RTC_UDR_SHIFT 0 +#define RTC_UDR_MASK (1 << RTC_UDR_SHIFT) +#define RTC_RBUDR_SHIFT 4 +#define RTC_RBUDR_MASK (1 << RTC_RBUDR_SHIFT) +/* WTSR and SMPL Register */ +#define WTSRT_SHIFT 0 +#define SMPLT_SHIFT 2 +#define WTSR_EN_SHIFT 6 +#define SMPL_EN_SHIFT 7 +#define WTSRT_MASK (3 << WTSRT_SHIFT) +#define SMPLT_MASK (3 << SMPLT_SHIFT) +#define WTSR_EN_MASK (1 << WTSR_EN_SHIFT) +#define SMPL_EN_MASK (1 << SMPL_EN_SHIFT) +/* RTC Hour register */ +#define HOUR_PM_SHIFT 6 +#define HOUR_PM_MASK (1 << HOUR_PM_SHIFT) +/* RTC Alarm Enable */ +#define ALARM_ENABLE_SHIFT 7 +#define ALARM_ENABLE_MASK (1 << ALARM_ENABLE_SHIFT) + +#define MAX77686_RTC_UPDATE_DELAY 16 + +enum { + RTC_SEC = 0, + RTC_MIN, + RTC_HOUR, + RTC_WEEKDAY, + RTC_MONTH, + RTC_YEAR, + RTC_DATE, + RTC_NR_TIME +}; + +struct max77686_rtc_info { + struct device *dev; + struct max77686_dev *max77686; + struct i2c_client *rtc; + struct rtc_device *rtc_dev; + struct mutex lock; + + struct regmap *regmap; + + int virq; + int rtc_24hr_mode; +}; + +enum MAX77686_RTC_OP { + MAX77686_RTC_WRITE, + MAX77686_RTC_READ, +}; + +static void max77686_rtc_data_to_tm(u8 *data, struct rtc_time *tm, + int rtc_24hr_mode) +{ + tm->tm_sec = data[RTC_SEC] & 0x7f; + tm->tm_min = data[RTC_MIN] & 0x7f; + if (rtc_24hr_mode) + tm->tm_hour = data[RTC_HOUR] & 0x1f; + else { + tm->tm_hour = data[RTC_HOUR] & 0x0f; + if (data[RTC_HOUR] & HOUR_PM_MASK) + tm->tm_hour += 12; + } + + tm->tm_wday = fls(data[RTC_WEEKDAY] & 0x7f) - 1; + tm->tm_mday = data[RTC_DATE] & 0x1f; + tm->tm_mon = (data[RTC_MONTH] & 0x0f) - 1; + tm->tm_year = (data[RTC_YEAR] & 0x7f) + 100; + tm->tm_yday = 0; + tm->tm_isdst = 0; +} + +static int max77686_rtc_tm_to_data(struct rtc_time *tm, u8 *data) +{ + data[RTC_SEC] = tm->tm_sec; + data[RTC_MIN] = tm->tm_min; + data[RTC_HOUR] = tm->tm_hour; + data[RTC_WEEKDAY] = 1 << tm->tm_wday; + data[RTC_DATE] = tm->tm_mday; + data[RTC_MONTH] = tm->tm_mon + 1; + data[RTC_YEAR] = tm->tm_year > 100 ? (tm->tm_year - 100) : 0 ; + + if (tm->tm_year < 100) { + pr_warn("%s: MAX77686 RTC cannot handle the year %d." + "Assume it's 2000.\n", __func__, + 1900 + tm->tm_year); + return -EINVAL; + } + return 0; +} + +static int max77686_rtc_update(struct max77686_rtc_info *info, + enum MAX77686_RTC_OP op) +{ + int ret; + unsigned int data; + + switch (op) { + case MAX77686_RTC_WRITE: + data = 1 << RTC_UDR_SHIFT; + break; + case MAX77686_RTC_READ: + data = 1 << RTC_RBUDR_SHIFT; + break; + } + + ret = regmap_update_bits(info->max77686->rtc_regmap, + MAX77686_RTC_UPDATE0, data, data); + if (ret < 0) + dev_err(info->dev, "%s: fail to write update reg(ret=%d, data=0x%x)\n", + __func__, ret, data); + else { + /* Minimum 16ms delay required before RTC update. */ + msleep(MAX77686_RTC_UPDATE_DELAY); + } + + return ret; +} + +static int max77686_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct max77686_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + int ret; + + mutex_lock(&info->lock); + + ret = max77686_rtc_update(info, MAX77686_RTC_READ); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(info->max77686->rtc_regmap, + MAX77686_RTC_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to read time reg(%d)\n", + __func__, ret); + goto out; + } + + max77686_rtc_data_to_tm(data, tm, info->rtc_24hr_mode); + + ret = rtc_valid_tm(tm); + +out: + mutex_unlock(&info->lock); + return ret; +} + +static int max77686_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct max77686_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + int ret; + + ret = max77686_rtc_tm_to_data(tm, data); + if (ret < 0) + return ret; + + mutex_lock(&info->lock); + + ret = regmap_bulk_write(info->max77686->rtc_regmap, + MAX77686_RTC_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write time reg(%d)\n", __func__, + ret); + goto out; + } + + ret = max77686_rtc_update(info, MAX77686_RTC_WRITE); + +out: + mutex_unlock(&info->lock); + return ret; +} + +static int max77686_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77686_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + unsigned int val; + int i, ret; + + mutex_lock(&info->lock); + + ret = max77686_rtc_update(info, MAX77686_RTC_READ); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(info->max77686->rtc_regmap, + MAX77686_ALARM1_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s:%d fail to read alarm reg(%d)\n", + __func__, __LINE__, ret); + goto out; + } + + max77686_rtc_data_to_tm(data, &alrm->time, info->rtc_24hr_mode); + + alrm->enabled = 0; + for (i = 0; i < RTC_NR_TIME; i++) { + if (data[i] & ALARM_ENABLE_MASK) { + alrm->enabled = 1; + break; + } + } + + alrm->pending = 0; + ret = regmap_read(info->max77686->regmap, MAX77686_REG_STATUS1, &val); + if (ret < 0) { + dev_err(info->dev, "%s:%d fail to read status1 reg(%d)\n", + __func__, __LINE__, ret); + goto out; + } + + if (val & (1 << 4)) /* RTCA1 */ + alrm->pending = 1; + +out: + mutex_unlock(&info->lock); + return 0; +} + +static int max77686_rtc_stop_alarm(struct max77686_rtc_info *info) +{ + u8 data[RTC_NR_TIME]; + int ret, i; + struct rtc_time tm; + + if (!mutex_is_locked(&info->lock)) + dev_warn(info->dev, "%s: should have mutex locked\n", __func__); + + ret = max77686_rtc_update(info, MAX77686_RTC_READ); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(info->max77686->rtc_regmap, + MAX77686_ALARM1_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to read alarm reg(%d)\n", + __func__, ret); + goto out; + } + + max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode); + + for (i = 0; i < RTC_NR_TIME; i++) + data[i] &= ~ALARM_ENABLE_MASK; + + ret = regmap_bulk_write(info->max77686->rtc_regmap, + MAX77686_ALARM1_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + + ret = max77686_rtc_update(info, MAX77686_RTC_WRITE); +out: + return ret; +} + +static int max77686_rtc_start_alarm(struct max77686_rtc_info *info) +{ + u8 data[RTC_NR_TIME]; + int ret; + struct rtc_time tm; + + if (!mutex_is_locked(&info->lock)) + dev_warn(info->dev, "%s: should have mutex locked\n", __func__); + + ret = max77686_rtc_update(info, MAX77686_RTC_READ); + if (ret < 0) + goto out; + + ret = regmap_bulk_read(info->max77686->rtc_regmap, + MAX77686_ALARM1_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to read alarm reg(%d)\n", + __func__, ret); + goto out; + } + + max77686_rtc_data_to_tm(data, &tm, info->rtc_24hr_mode); + + data[RTC_SEC] |= (1 << ALARM_ENABLE_SHIFT); + data[RTC_MIN] |= (1 << ALARM_ENABLE_SHIFT); + data[RTC_HOUR] |= (1 << ALARM_ENABLE_SHIFT); + data[RTC_WEEKDAY] &= ~ALARM_ENABLE_MASK; + if (data[RTC_MONTH] & 0xf) + data[RTC_MONTH] |= (1 << ALARM_ENABLE_SHIFT); + if (data[RTC_YEAR] & 0x7f) + data[RTC_YEAR] |= (1 << ALARM_ENABLE_SHIFT); + if (data[RTC_DATE] & 0x1f) + data[RTC_DATE] |= (1 << ALARM_ENABLE_SHIFT); + + ret = regmap_bulk_write(info->max77686->rtc_regmap, + MAX77686_ALARM1_SEC, data, RTC_NR_TIME); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + + ret = max77686_rtc_update(info, MAX77686_RTC_WRITE); +out: + return ret; +} + +static int max77686_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct max77686_rtc_info *info = dev_get_drvdata(dev); + u8 data[RTC_NR_TIME]; + int ret; + + ret = max77686_rtc_tm_to_data(&alrm->time, data); + if (ret < 0) + return ret; + + mutex_lock(&info->lock); + + ret = max77686_rtc_stop_alarm(info); + if (ret < 0) + goto out; + + ret = regmap_bulk_write(info->max77686->rtc_regmap, + MAX77686_ALARM1_SEC, data, RTC_NR_TIME); + + if (ret < 0) { + dev_err(info->dev, "%s: fail to write alarm reg(%d)\n", + __func__, ret); + goto out; + } + + ret = max77686_rtc_update(info, MAX77686_RTC_WRITE); + if (ret < 0) + goto out; + + if (alrm->enabled) + ret = max77686_rtc_start_alarm(info); +out: + mutex_unlock(&info->lock); + return ret; +} + +static int max77686_rtc_alarm_irq_enable(struct device *dev, + unsigned int enabled) +{ + struct max77686_rtc_info *info = dev_get_drvdata(dev); + int ret; + + mutex_lock(&info->lock); + if (enabled) + ret = max77686_rtc_start_alarm(info); + else + ret = max77686_rtc_stop_alarm(info); + mutex_unlock(&info->lock); + + return ret; +} + +static irqreturn_t max77686_rtc_alarm_irq(int irq, void *data) +{ + struct max77686_rtc_info *info = data; + + dev_info(info->dev, "%s:irq(%d)\n", __func__, irq); + + rtc_update_irq(info->rtc_dev, 1, RTC_IRQF | RTC_AF); + + return IRQ_HANDLED; +} + +static const struct rtc_class_ops max77686_rtc_ops = { + .read_time = max77686_rtc_read_time, + .set_time = max77686_rtc_set_time, + .read_alarm = max77686_rtc_read_alarm, + .set_alarm = max77686_rtc_set_alarm, + .alarm_irq_enable = max77686_rtc_alarm_irq_enable, +}; + +#ifdef MAX77686_RTC_WTSR +static void max77686_rtc_enable_wtsr(struct max77686_rtc_info *info, + bool enable) +{ + int ret; + unsigned int val, mask; + + if (enable) + val = (1 << WTSR_EN_SHIFT) | (3 << WTSRT_SHIFT); + else + val = 0; + + mask = WTSR_EN_MASK | WTSRT_MASK; + + dev_info(info->dev, "%s: %s WTSR\n", __func__, + enable ? "enable" : "disable"); + + ret = regmap_update_bits(info->max77686->rtc_regmap, + MAX77686_WTSR_SMPL_CNTL, mask, val); + if (ret < 0) { + dev_err(info->dev, "%s: fail to update WTSR reg(%d)\n", + __func__, ret); + return; + } + + max77686_rtc_update(info, MAX77686_RTC_WRITE); +} +#else +static void max77686_rtc_enable_wtsr(struct max77686_rtc_info *info, + bool enable) {} +#endif /* MAX77686_RTC_WTSR */ +#ifdef MAX77686_RTC_SMPL +static void max77686_rtc_enable_smpl(struct max77686_rtc_info *info, + bool enable) +{ + int ret; + unsigned int val, mask; + + if (enable) + val = (1 << SMPL_EN_SHIFT) | (0 << SMPLT_SHIFT); + else + val = 0; + + mask = SMPL_EN_MASK | SMPLT_MASK; + + dev_info(info->dev, "%s: %s SMPL\n", __func__, + enable ? "enable" : "disable"); + + ret = regmap_update_bits(info->max77686->rtc_regmap, + MAX77686_WTSR_SMPL_CNTL, mask, val); + if (ret < 0) { + dev_err(info->dev, "%s: fail to update SMPL reg(%d)\n", + __func__, ret); + return; + } + + max77686_rtc_update(info, MAX77686_RTC_WRITE); + + val = 0; + regmap_read(info->max77686->rtc_regmap, MAX77686_WTSR_SMPL_CNTL, &val); + pr_info("%s: WTSR_SMPL(0x%02x)\n", __func__, val); +} +#else +static void max77686_rtc_enable_smpl(struct max77686_rtc_info *info, + bool enable) {} +#endif /* MAX77686_RTC_SMPL */ + +static int max77686_rtc_init_reg(struct max77686_rtc_info *info) +{ + u8 data[2]; + int ret; + + /* Set RTC control register : Binary mode, 24hour mdoe */ + data[0] = (1 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT); + data[1] = (0 << BCD_EN_SHIFT) | (1 << MODEL24_SHIFT); + + info->rtc_24hr_mode = 1; + + ret = regmap_bulk_write(info->max77686->rtc_regmap, + MAX77686_RTC_CONTROLM, data, 2); + if (ret < 0) { + dev_err(info->dev, "%s: fail to write controlm reg(%d)\n", + __func__, ret); + return ret; + } + + ret = max77686_rtc_update(info, MAX77686_RTC_WRITE); + return ret; +} + +static struct regmap_config max77686_rtc_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +}; + +static int max77686_rtc_probe(struct platform_device *pdev) +{ + struct max77686_dev *max77686 = dev_get_drvdata(pdev->dev.parent); + struct max77686_rtc_info *info; + int ret, virq; + + pr_info("%s\n", __func__); + + info = kzalloc(sizeof(struct max77686_rtc_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + mutex_init(&info->lock); + info->dev = &pdev->dev; + info->max77686 = max77686; + info->rtc = max77686->rtc; + info->max77686->rtc_regmap = regmap_init_i2c(info->max77686->rtc, + &max77686_rtc_regmap_config); + if (IS_ERR(info->max77686->rtc_regmap)) { + ret = PTR_ERR(info->max77686->rtc_regmap); + dev_err(info->max77686->dev, "Failed to allocate register map: %d\n", + ret); + kfree(info); + return ret; + } + platform_set_drvdata(pdev, info); + + ret = max77686_rtc_init_reg(info); + + if (ret < 0) { + dev_err(&pdev->dev, "Failed to initialize RTC reg:%d\n", ret); + goto err_rtc; + } + + max77686_rtc_enable_wtsr(info, true); + max77686_rtc_enable_smpl(info, true); + + device_init_wakeup(&pdev->dev, 1); + + info->rtc_dev = rtc_device_register("max77686-rtc", &pdev->dev, + &max77686_rtc_ops, THIS_MODULE); + + if (IS_ERR(info->rtc_dev)) { + pr_info("%s: fail\n", __func__); + + ret = PTR_ERR(info->rtc_dev); + dev_err(&pdev->dev, "Failed to register RTC device: %d\n", ret); + if (ret == 0) + ret = -EINVAL; + goto err_rtc; + } + virq = irq_create_mapping(max77686->irq_domain, MAX77686_RTCIRQ_RTCA1); + if (!virq) + goto err_rtc; + info->virq = virq; + + ret = request_threaded_irq(virq, NULL, max77686_rtc_alarm_irq, 0, + "rtc-alarm0", info); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request alarm IRQ: %d: %d\n", + info->virq, ret); + goto err_rtc; + } + + goto out; +err_rtc: + kfree(info); + return ret; +out: + return ret; +} + +static int max77686_rtc_remove(struct platform_device *pdev) +{ + struct max77686_rtc_info *info = platform_get_drvdata(pdev); + + if (info) { + free_irq(info->virq, info); + rtc_device_unregister(info->rtc_dev); + kfree(info); + } + + return 0; +} + +static void max77686_rtc_shutdown(struct platform_device *pdev) +{ + struct max77686_rtc_info *info = platform_get_drvdata(pdev); + int i; + u8 val = 0; + + for (i = 0; i < 3; i++) { + max77686_rtc_enable_wtsr(info, false); + regmap_read(info->max77686->rtc_regmap, + MAX77686_WTSR_SMPL_CNTL, &val); + pr_info("%s: WTSR_SMPL reg(0x%02x)\n", __func__, val); + if (val & WTSR_EN_MASK) + pr_emerg("%s: fail to disable WTSR\n", __func__); + else { + pr_info("%s: success to disable WTSR\n", __func__); + break; + } + } + + max77686_rtc_enable_smpl(info, false); +} + +static const struct platform_device_id rtc_id[] = { + { "max77686-rtc", 0 }, + {}, +}; + +static struct platform_driver max77686_rtc_driver = { + .driver = { + .name = "max77686-rtc", + .owner = THIS_MODULE, + }, + .probe = max77686_rtc_probe, + .remove = max77686_rtc_remove, + .shutdown = max77686_rtc_shutdown, + .id_table = rtc_id, +}; + +static int __init max77686_rtc_init(void) +{ + return platform_driver_register(&max77686_rtc_driver); +} +module_init(max77686_rtc_init); + +static void __exit max77686_rtc_exit(void) +{ + platform_driver_unregister(&max77686_rtc_driver); +} +module_exit(max77686_rtc_exit); + +MODULE_DESCRIPTION("Maxim MAX77686 RTC driver"); +MODULE_AUTHOR(""); +MODULE_LICENSE("GPL");