blob: 62dcd25613edf4b8ae6b97b3d6555e18b123a214 [file] [log] [blame]
/*
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2022 StarFive
*
* Authors:
* Wei Liang Lim <weiliang.lim@starfivetech.com>
* Minda Chen <minda.chen@starfivetech.com>
*/
#include <libfdt.h>
#include <platform_override.h>
#include <sbi/sbi_error.h>
#include <sbi/sbi_bitops.h>
#include <sbi/sbi_system.h>
#include <sbi/sbi_console.h>
#include <sbi/sbi_timer.h>
#include <sbi/riscv_io.h>
#include <sbi_utils/fdt/fdt_helper.h>
#include <sbi_utils/reset/fdt_reset.h>
#include <sbi_utils/i2c/fdt_i2c.h>
struct pmic {
struct i2c_adapter *adapter;
u32 dev_addr;
};
struct jh7110 {
u64 pmu_reg_base;
u64 clk_reg_base;
u32 i2c_clk_offset;
};
static struct pmic pmic_inst;
static struct jh7110 jh7110_inst;
static u32 selected_hartid = -1;
/* PMU register define */
#define HW_EVENT_TURN_ON_MASK 0x04
#define HW_EVENT_TURN_OFF_MASK 0x08
#define SW_TURN_ON_POWER_MODE 0x0C
#define SW_TURN_OFF_POWER_MODE 0x10
#define SW_ENCOURAGE 0x44
#define PMU_INT_MASK 0x48
#define PCH_BYPASS 0x4C
#define PCH_PSTATE 0x50
#define PCH_TIMEOUT 0x54
#define LP_TIMEOUT 0x58
#define HW_TURN_ON_MODE 0x5C
#define CURR_POWER_MODE 0x80
#define PMU_EVENT_STATUS 0x88
#define PMU_INT_STATUS 0x8C
/* sw encourage cfg */
#define SW_MODE_ENCOURAGE_EN_LO 0x05
#define SW_MODE_ENCOURAGE_EN_HI 0x50
#define SW_MODE_ENCOURAGE_DIS_LO 0x0A
#define SW_MODE_ENCOURAGE_DIS_HI 0xA0
#define SW_MODE_ENCOURAGE_ON 0xFF
#define DEVICE_PD_MASK 0xfc
#define SYSTOP_CPU_PD_MASK 0x3
#define TIMEOUT_COUNT 100000
#define AXP15060_POWER_REG 0x32
#define AXP15060_POWER_OFF_BIT BIT(7)
#define AXP15060_RESET_BIT BIT(6)
#define I2C_APB_CLK_ENABLE_BIT BIT(31)
static int pm_system_reset_check(u32 type, u32 reason)
{
switch (type) {
case SBI_SRST_RESET_TYPE_SHUTDOWN:
return 1;
case SBI_SRST_RESET_TYPE_COLD_REBOOT:
return 255;
default:
break;
}
return 0;
}
static int wait_pmu_pd_state(u32 mask)
{
int count = 0;
unsigned long addr = jh7110_inst.pmu_reg_base;
u32 val;
do {
val = readl((void *)(addr + CURR_POWER_MODE));
if (val == mask)
return 0;
sbi_timer_udelay(2);
count += 1;
if (count == TIMEOUT_COUNT)
return SBI_ETIMEDOUT;
} while (1);
}
static int shutdown_device_power_domain(void)
{
unsigned long addr = jh7110_inst.pmu_reg_base;
u32 curr_mode;
int ret = 0;
curr_mode = readl((void *)(addr + CURR_POWER_MODE));
curr_mode &= DEVICE_PD_MASK;
if (curr_mode) {
writel(curr_mode, (void *)(addr + SW_TURN_OFF_POWER_MODE));
writel(SW_MODE_ENCOURAGE_ON, (void *)(addr + SW_ENCOURAGE));
writel(SW_MODE_ENCOURAGE_DIS_LO, (void *)(addr + SW_ENCOURAGE));
writel(SW_MODE_ENCOURAGE_DIS_HI, (void *)(addr + SW_ENCOURAGE));
ret = wait_pmu_pd_state(SYSTOP_CPU_PD_MASK);
if (ret)
sbi_printf("%s shutdown device power %x error\n",
__func__, curr_mode);
}
return ret;
}
static void pmic_ops(struct pmic *pmic, int type)
{
int ret = 0;
u8 val;
ret = shutdown_device_power_domain();
if (ret)
return;
ret = i2c_adapter_reg_read(pmic->adapter, pmic->dev_addr,
AXP15060_POWER_REG, &val);
if (ret) {
sbi_printf("%s: cannot read pmic power register\n", __func__);
return;
}
val |= AXP15060_POWER_OFF_BIT;
if (type == SBI_SRST_RESET_TYPE_SHUTDOWN)
val |= AXP15060_POWER_OFF_BIT;
else
val |= AXP15060_RESET_BIT;
ret = i2c_adapter_reg_write(pmic->adapter, pmic->dev_addr,
AXP15060_POWER_REG, val);
if (ret)
sbi_printf("%s: cannot write pmic power register\n", __func__);
}
static void pmic_i2c_clk_enable(void)
{
unsigned long clock_base;
unsigned int val;
clock_base = jh7110_inst.clk_reg_base + jh7110_inst.i2c_clk_offset;
val = readl((void *)clock_base);
if (!val)
writel(I2C_APB_CLK_ENABLE_BIT, (void *)clock_base);
}
static void pm_system_reset(u32 type, u32 reason)
{
if (pmic_inst.adapter) {
switch (type) {
case SBI_SRST_RESET_TYPE_SHUTDOWN:
case SBI_SRST_RESET_TYPE_COLD_REBOOT:
/* i2c clk may be disabled by kernel driver */
pmic_i2c_clk_enable();
pmic_ops(&pmic_inst, type);
break;
default:
break;
}
}
sbi_hart_hang();
}
static struct sbi_system_reset_device pm_reset = {
.name = "pm-reset",
.system_reset_check = pm_system_reset_check,
.system_reset = pm_system_reset
};
static int starfive_jh7110_inst_init(void *fdt);
static int pm_reset_init(void *fdt, int nodeoff,
const struct fdt_match *match)
{
int rc;
int i2c_bus;
struct i2c_adapter *adapter;
u64 addr;
rc = fdt_get_node_addr_size(fdt, nodeoff, 0, &addr, NULL);
if (rc)
return rc;
pmic_inst.dev_addr = addr;
i2c_bus = fdt_parent_offset(fdt, nodeoff);
if (i2c_bus < 0)
return i2c_bus;
/* i2c adapter get */
rc = fdt_i2c_adapter_get(fdt, i2c_bus, &adapter);
if (rc)
return rc;
pmic_inst.adapter = adapter;
rc = starfive_jh7110_inst_init(fdt);
if (rc)
return rc;
sbi_system_reset_add_device(&pm_reset);
return 0;
}
static const struct fdt_match pm_reset_match[] = {
{ .compatible = "x-powers,axp15060", .data = (void *)true },
{ },
};
static struct fdt_reset fdt_reset_pmic = {
.match_table = pm_reset_match,
.init = pm_reset_init,
};
static int starfive_jh7110_inst_init(void *fdt)
{
int noff, rc = 0;
const fdt32_t *val;
int len;
u64 addr;
noff = fdt_node_offset_by_compatible(fdt, -1, "starfive,jh7110-pmu");
if (-1 < noff) {
rc = fdt_get_node_addr_size(fdt, noff, 0, &addr, NULL);
if (rc)
goto err;
jh7110_inst.pmu_reg_base = addr;
} else {
return -SBI_ENODEV;
}
noff = fdt_node_offset_by_compatible(fdt, -1, "starfive,jh7110-syscrg");
if (-1 < noff) {
rc = fdt_get_node_addr_size(fdt, noff, 0, &addr, NULL);
if (rc)
goto err;
jh7110_inst.clk_reg_base = addr;
} else {
return -SBI_ENODEV;
}
if (pmic_inst.adapter) {
/*
* The clocks property looks like this:
* clocks = <&syscrg JH7110_SYSCLK_I2C5_APB>;
*
* So, check that the length is 8 bytes, and get
* the offset from the second value.
*/
val = fdt_getprop(fdt, pmic_inst.adapter->id, "clocks", &len);
if (val && len == 8)
jh7110_inst.i2c_clk_offset = fdt32_to_cpu(val[1]) << 2;
else
rc = SBI_EINVAL;
}
err:
return rc;
}
static int starfive_jh7110_final_init(bool cold_boot,
const struct fdt_match *match)
{
void *fdt = fdt_get_address();
if (cold_boot) {
fdt_reset_driver_init(fdt, &fdt_reset_pmic);
}
return 0;
}
static bool starfive_jh7110_cold_boot_allowed(u32 hartid,
const struct fdt_match *match)
{
if (selected_hartid != -1)
return (selected_hartid == hartid);
return true;
}
static void starfive_jh7110_fw_init(void *fdt, const struct fdt_match *match)
{
const fdt32_t *val;
int len, coff;
coff = fdt_path_offset(fdt, "/chosen");
if (-1 < coff) {
val = fdt_getprop(fdt, coff, "starfive,boot-hart-id", &len);
if (val && len >= sizeof(fdt32_t))
selected_hartid = (u32) fdt32_to_cpu(*val);
}
}
static const struct fdt_match starfive_jh7110_match[] = {
{ .compatible = "starfive,jh7110" },
{ },
};
const struct platform_override starfive_jh7110 = {
.match_table = starfive_jh7110_match,
.cold_boot_allowed = starfive_jh7110_cold_boot_allowed,
.fw_init = starfive_jh7110_fw_init,
.final_init = starfive_jh7110_final_init,
};