1. Add standard rtc driver. 2. Add rtc misc driver for additional support. 3. Add 32k control function. 4. Add power-on alarm support. MTK-Commit-Id: f3e881935a461ec8f1fe4839855e425e4cd18a8f Change-Id: I4755fd5e2af6acd41248208515a8165585df1ae9 Signed-off-by: Ran Bi <ran.bi@mediatek.com> CR-Id: ALPS04683963 Feature: RTC-Power Drop Auto Reboot
344 lines
7.3 KiB
C
344 lines
7.3 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/init.h>
|
|
#include <linux/module.h>
|
|
#include <linux/regmap.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/irqdomain.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/io.h>
|
|
#include <linux/mfd/mt6397/core.h>
|
|
#include <linux/mfd/mt6397/rtc_misc.h>
|
|
#include <linux/pm.h>
|
|
|
|
#define RTC_BBPU 0x0000
|
|
#define RTC_BBPU_CBUSY BIT(6)
|
|
|
|
#define RTC_WRTGR 0x003c
|
|
|
|
#define RTC_AL_HOU 0x001c
|
|
#define RTC_PDN1 0x002c
|
|
#define RTC_PDN2 0x002e
|
|
#define RTC_SPAR0 0x0030
|
|
#define RTC_CON 0x003e
|
|
#define RTC_BBPU_KEY (0x43 << 8)
|
|
#define RTC_BBPU_AUTO BIT(3)
|
|
#define RTC_BBPU_PWREN BIT(0)
|
|
#define RTC_CON_F32KOB BIT(5)
|
|
#define RTC_GPIO_USER_MASK 0x1f00
|
|
|
|
static const u16 rtc_spare_reg[][3] = {
|
|
{RTC_AL_HOU, 0x7f, 8},
|
|
{RTC_PDN1, 0xf, 0},
|
|
{RTC_PDN1, 0x3, 4},
|
|
{RTC_PDN1, 0x1, 6},
|
|
{RTC_PDN1, 0x1, 7},
|
|
{RTC_PDN1, 0x1, 13},
|
|
{RTC_PDN1, 0x1, 14},
|
|
{RTC_PDN1, 0x1, 15},
|
|
{RTC_PDN2, 0x1, 4},
|
|
{RTC_PDN2, 0x3, 5},
|
|
{RTC_PDN2, 0x1, 7},
|
|
{RTC_PDN2, 0x1, 15},
|
|
{RTC_SPAR0, 0x1, 6},
|
|
{RTC_SPAR0, 0x1, 7}
|
|
};
|
|
|
|
enum rtc_spare_enum {
|
|
RTC_FGSOC = 0,
|
|
RTC_ANDROID,
|
|
RTC_FAC_RESET,
|
|
RTC_BYPASS_PWR,
|
|
RTC_PWRON_TIME,
|
|
RTC_FAST_BOOT,
|
|
RTC_KPOC,
|
|
RTC_DEBUG,
|
|
RTC_PWRON_AL,
|
|
RTC_UART,
|
|
RTC_AUTOBOOT,
|
|
RTC_PWRON_LOGO,
|
|
RTC_32K_LESS,
|
|
RTC_LP_DET,
|
|
RTC_SPAR_NUM
|
|
};
|
|
|
|
enum rtc_reg_set {
|
|
RTC_REG,
|
|
RTC_MASK,
|
|
RTC_SHIFT
|
|
};
|
|
|
|
struct mt6397_misc {
|
|
struct device *dev;
|
|
struct mutex lock;
|
|
struct regmap *regmap;
|
|
u32 addr_base;
|
|
};
|
|
|
|
static struct mt6397_misc *rtc_misc;
|
|
|
|
static int mtk_rtc_write_trigger(void)
|
|
{
|
|
unsigned long timeout = jiffies + HZ;
|
|
int ret;
|
|
u32 data;
|
|
|
|
ret = regmap_write(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_WRTGR, 1);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
while (1) {
|
|
ret = regmap_read(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_BBPU, &data);
|
|
if (ret < 0)
|
|
break;
|
|
if (!(data & RTC_BBPU_CBUSY))
|
|
break;
|
|
if (time_after(jiffies, timeout)) {
|
|
ret = -ETIMEDOUT;
|
|
break;
|
|
}
|
|
cpu_relax();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static u32 __mtk_misc_get_spare_register(enum rtc_spare_enum cmd)
|
|
{
|
|
int ret;
|
|
u32 data;
|
|
|
|
if (cmd >= 0 && cmd < RTC_SPAR_NUM) {
|
|
ret = regmap_read(rtc_misc->regmap,
|
|
rtc_misc->addr_base
|
|
+ rtc_spare_reg[cmd][RTC_REG], &data);
|
|
|
|
data = (data >> rtc_spare_reg[cmd][RTC_SHIFT]) &
|
|
rtc_spare_reg[cmd][RTC_MASK];
|
|
return data;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
static void __mtk_misc_set_spare_register(enum rtc_spare_enum cmd, u32 val)
|
|
{
|
|
int ret;
|
|
u32 data, mask;
|
|
|
|
if (cmd >= 0 && cmd < RTC_SPAR_NUM) {
|
|
data = val << rtc_spare_reg[cmd][RTC_SHIFT];
|
|
mask = rtc_spare_reg[cmd][RTC_MASK]
|
|
<< rtc_spare_reg[cmd][RTC_SHIFT];
|
|
ret = regmap_update_bits(rtc_misc->regmap,
|
|
rtc_misc->addr_base + rtc_spare_reg[cmd][RTC_REG],
|
|
mask, data);
|
|
if (ret < 0)
|
|
dev_err(rtc_misc->dev, "regmap write error!!!\n");
|
|
|
|
mtk_rtc_write_trigger();
|
|
}
|
|
}
|
|
|
|
|
|
u32 mtk_misc_get_spare_fg_value(void)
|
|
{
|
|
u32 data;
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
data = __mtk_misc_get_spare_register(RTC_FGSOC);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
return data;
|
|
}
|
|
|
|
int mtk_misc_set_spare_fg_value(u32 val)
|
|
{
|
|
if (val > 100)
|
|
return 1;
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
__mtk_misc_set_spare_register(RTC_FGSOC, val);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
return 0;
|
|
}
|
|
|
|
bool mtk_misc_crystal_exist_status(void)
|
|
{
|
|
u32 ret;
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
ret = __mtk_misc_get_spare_register(RTC_32K_LESS);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
return !!ret;
|
|
}
|
|
EXPORT_SYMBOL(mtk_misc_crystal_exist_status);
|
|
|
|
static void mtk_misc_set_gpio_32k_status(enum rtc_gpio_user_t user, bool enable)
|
|
{
|
|
u32 pdn1, temp, con;
|
|
int ret;
|
|
|
|
ret = regmap_read(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_PDN1, &pdn1);
|
|
if (ret < 0)
|
|
goto exit;
|
|
ret = regmap_read(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_CON, &con);
|
|
if (ret < 0)
|
|
goto exit;
|
|
|
|
if (!enable) {
|
|
temp = pdn1 & ~(1 << user);
|
|
ret = regmap_write(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_PDN1, temp);
|
|
if (ret < 0)
|
|
goto exit;
|
|
mtk_rtc_write_trigger();
|
|
if (!(pdn1 & RTC_GPIO_USER_MASK))
|
|
con |= RTC_CON_F32KOB;
|
|
} else {
|
|
con &= ~RTC_CON_F32KOB;
|
|
pdn1 |= (1 << user);
|
|
ret = regmap_write(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_PDN1, pdn1);
|
|
if (ret < 0)
|
|
goto exit;
|
|
mtk_rtc_write_trigger();
|
|
}
|
|
|
|
ret = regmap_write(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_CON, con);
|
|
if (ret < 0)
|
|
goto exit;
|
|
mtk_rtc_write_trigger();
|
|
|
|
return;
|
|
exit:
|
|
dev_err(rtc_misc->dev, "regmap write/read error!!!\n");
|
|
}
|
|
|
|
void rtc_gpio_enable_32k(enum rtc_gpio_user_t user)
|
|
{
|
|
if (user < RTC_GPIO_USER_WIFI || user > RTC_GPIO_USER_PMIC)
|
|
return;
|
|
dev_info(rtc_misc->dev, "enable 32k clock output!!!\n");
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
mtk_misc_set_gpio_32k_status(user, true);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
}
|
|
EXPORT_SYMBOL(rtc_gpio_enable_32k);
|
|
|
|
void rtc_gpio_disable_32k(enum rtc_gpio_user_t user)
|
|
{
|
|
if (user < RTC_GPIO_USER_WIFI || user > RTC_GPIO_USER_PMIC)
|
|
return;
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
mtk_misc_set_gpio_32k_status(user, false);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
}
|
|
EXPORT_SYMBOL(rtc_gpio_disable_32k);
|
|
|
|
bool mtk_misc_low_power_detected(void)
|
|
{
|
|
u32 ret;
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
ret = __mtk_misc_get_spare_register(RTC_LP_DET);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
return !!ret;
|
|
}
|
|
EXPORT_SYMBOL(mtk_misc_low_power_detected);
|
|
|
|
void mtk_misc_mark_recovery(void)
|
|
{
|
|
mutex_lock(&rtc_misc->lock);
|
|
__mtk_misc_set_spare_register(RTC_FAC_RESET, 0x1);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
}
|
|
|
|
void mtk_misc_mark_fast(void)
|
|
{
|
|
mutex_lock(&rtc_misc->lock);
|
|
__mtk_misc_set_spare_register(RTC_FAST_BOOT, 0x1);
|
|
mutex_unlock(&rtc_misc->lock);
|
|
}
|
|
|
|
static void mt_power_off(void)
|
|
{
|
|
u32 bbpu;
|
|
int ret;
|
|
|
|
mutex_lock(&rtc_misc->lock);
|
|
bbpu = RTC_BBPU_KEY | RTC_BBPU_AUTO | RTC_BBPU_PWREN;
|
|
ret = regmap_write(rtc_misc->regmap,
|
|
rtc_misc->addr_base + RTC_BBPU, bbpu);
|
|
if (ret < 0)
|
|
dev_err(rtc_misc->dev, "regmap write error!!!\n");
|
|
mtk_rtc_write_trigger();
|
|
mutex_unlock(&rtc_misc->lock);
|
|
}
|
|
|
|
static int mt6397_misc_probe(struct platform_device *pdev)
|
|
{
|
|
struct resource *res;
|
|
struct device_node *np = pdev->dev.parent->of_node;
|
|
struct mt6397_chip *mt6397_chip = dev_get_drvdata(pdev->dev.parent);
|
|
struct mt6397_misc *misc;
|
|
int pm_off = 0;
|
|
|
|
if (!np)
|
|
return -EINVAL;
|
|
|
|
misc = devm_kzalloc(&pdev->dev, sizeof(struct mt6397_misc), GFP_KERNEL);
|
|
if (!misc)
|
|
return -ENOMEM;
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
misc->addr_base = res->start;
|
|
|
|
misc->regmap = mt6397_chip->regmap;
|
|
misc->dev = &pdev->dev;
|
|
mutex_init(&misc->lock);
|
|
rtc_misc = misc;
|
|
platform_set_drvdata(pdev, misc);
|
|
|
|
pm_off = of_property_read_bool(np,
|
|
"mediatek,system-power-controller");
|
|
if (pm_off && !pm_power_off)
|
|
pm_power_off = mt_power_off;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id mt6397_misc_of_match[] = {
|
|
{ .compatible = "mediatek,mt6397-misc", },
|
|
{ .compatible = "mediatek,mt6323-misc", },
|
|
{ .compatible = "mediatek,mt6392-misc", },
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, mt6397_misc_of_match);
|
|
|
|
static struct platform_driver mt6397_misc_driver = {
|
|
.driver = {
|
|
.name = "mt6397-misc",
|
|
.of_match_table = mt6397_misc_of_match,
|
|
},
|
|
.probe = mt6397_misc_probe,
|
|
};
|
|
|
|
module_platform_driver(mt6397_misc_driver);
|
|
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_AUTHOR("Tianping Fang <tianping.fang@mediatek.com>");
|
|
MODULE_DESCRIPTION("Misc Driver for MediaTek MT6323 PMIC");
|
|
MODULE_ALIAS("platform:mt6397-misc");
|