blob: e5b9bfc43e631a40d23ede68aab62c7a5639296f [file] [log] [blame]
// 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");