diff options
Diffstat (limited to 'arch')
-rw-r--r-- | arch/arm/boot/dts/exynos4412-trats2.dts | 6 | ||||
-rw-r--r-- | arch/arm/mach-exynos/Makefile | 2 | ||||
-rw-r--r-- | arch/arm/mach-exynos/include/mach/regs-pmu.h | 2 | ||||
-rw-r--r-- | arch/arm/mach-exynos/sec-reboot.c | 272 |
4 files changed, 282 insertions, 0 deletions
diff --git a/arch/arm/boot/dts/exynos4412-trats2.dts b/arch/arm/boot/dts/exynos4412-trats2.dts index fa6fa4006ad..ff08a4b2a9a 100644 --- a/arch/arm/boot/dts/exynos4412-trats2.dts +++ b/arch/arm/boot/dts/exynos4412-trats2.dts @@ -1512,6 +1512,12 @@ status = "okay"; max-pixel-clock = <75000000>; }; + + sec_reboot@ { + compatible = "samsung,sec-reboot"; + power-gpio = <&gpx2 7 1>; + extcon = <&max_muic>; + }; }; &pinctrl_1 { diff --git a/arch/arm/mach-exynos/Makefile b/arch/arm/mach-exynos/Makefile index 9e63a197349..0b539ea1bda 100644 --- a/arch/arm/mach-exynos/Makefile +++ b/arch/arm/mach-exynos/Makefile @@ -46,6 +46,8 @@ obj-$(CONFIG_MACH_SMDK4412) += mach-smdk4x12.o obj-$(CONFIG_MACH_EXYNOS4_DT) += mach-exynos4-dt.o obj-$(CONFIG_MACH_EXYNOS5_DT) += mach-exynos5-dt.o +obj-$(CONFIG_ARCH_EXYNOS4) += sec-reboot.o + # device support obj-y += dev-uart.o diff --git a/arch/arm/mach-exynos/include/mach/regs-pmu.h b/arch/arm/mach-exynos/include/mach/regs-pmu.h index 3820e399913..1b888cdd6e0 100644 --- a/arch/arm/mach-exynos/include/mach/regs-pmu.h +++ b/arch/arm/mach-exynos/include/mach/regs-pmu.h @@ -149,6 +149,8 @@ #define S5P_PAD_RET_EBIA_OPTION S5P_PMUREG(0x3188) #define S5P_PAD_RET_EBIB_OPTION S5P_PMUREG(0x31A8) +#define S5P_PS_HOLD_CONTROL S5P_PMUREG(0x330C) + #define S5P_PMU_CAM_CONF S5P_PMUREG(0x3C00) #define S5P_PMU_TV_CONF S5P_PMUREG(0x3C20) #define S5P_PMU_MFC_CONF S5P_PMUREG(0x3C40) diff --git a/arch/arm/mach-exynos/sec-reboot.c b/arch/arm/mach-exynos/sec-reboot.c new file mode 100644 index 00000000000..b53e9b73a47 --- /dev/null +++ b/arch/arm/mach-exynos/sec-reboot.c @@ -0,0 +1,272 @@ +#include <linux/delay.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <asm/cacheflush.h> +#include <asm/system.h> +#include <mach/regs-pmu.h> +#include <linux/gpio.h> +#include "common.h" +#include <linux/extcon.h> +#include <linux/of.h> +#include <linux/of_gpio.h> + +struct sec_reboot_platform_data { + struct device *dev; + struct extcon_dev *edev; + int power_button; +}; + +static struct sec_reboot_platform_data *g_pdata; + +enum cable_type { + NONE = 0, + CHARGER, + UART, +}; + +static int is_cable_attached(void) +{ +#ifdef CONFIG_EXTCON + /* Use extcon subsystem to check state of jig cable */ + static char *cables[] = { + /* + * FIXME: This function has strong dependency on extcon provider + * (muic device) because of unfixed cable name among all extcon + * provider. It has potential issue ,if board use different muic + * device for controlling external cable. So, the dependency + * between sec-reboot.c and extcon device driver will be removed + * by using attribute feature of each cable. + */ + "USB", + "TA", + /* + * JIG UART cable makes JIGONB pin low and then PMIC will ignore + * PS_HOLD pin state. As a result, power-off try will be failed. + */ + "JIG-UART-OFF", + }; + int i; + + /* Use extcon subsystem to check the state of cable */ + for (i = 0 ; i < ARRAY_SIZE(cables) ; i++) { + if (g_pdata && + extcon_get_cable_state(g_pdata->edev, cables[i])) { + if (!strncmp(cables[i], "JIG-UART", 8)) + return UART; + else + return CHARGER; + } + } +#endif + /* None of cables are attached */ + return NONE; +} + +static void sec_power_off(void) +{ + int cable, poweroff_try = 0; + + if (g_pdata && !g_pdata->edev) { + g_pdata->edev = extcon_get_edev_by_phandle(g_pdata->dev, 0); + if (IS_ERR(g_pdata->edev)) { + dev_err(g_pdata->dev, "couldn't get extcon device\n"); + return; + } + } + + local_irq_disable(); + + while (1) { + /* Check reboot charging */ + if ((cable = is_cable_attached()) || (poweroff_try >= 5)) { + pr_emerg("%s : Can't power off, reboot!" + " (cable=%d, poweroff_try=%d)\n", + __func__, cable, poweroff_try); + + /* To enter LP charging */ + if (cable == CHARGER) + writel(0x0, S5P_INFORM2); + + flush_cache_all(); + outer_flush_all(); + + exynos4_restart(0, 0); + + pr_emerg("%s: waiting for reboot\n", __func__); + while (1) + ; + } + + /* Check if power button is released. */ + if (!g_pdata || gpio_get_value(g_pdata->power_button)) { + /* + * Power-off code for EXYNOS + * EXYNOS series can be power off by setting PS_HOLD pin + * from High to Low. The pin state can be set via PMU's + * register, PS_HOLD_CONTROL(R/W, 0x1002_330C). + */ + pr_emerg("%s: set PS_HOLD low\n", __func__); + writel(readl(S5P_PS_HOLD_CONTROL) & 0xFFFFFEFF, + S5P_PS_HOLD_CONTROL); + + pr_emerg + ("%s: Should not reach here! (poweroff_try:%d)\n", + __func__, ++poweroff_try); + } else { + /* if power button is not released, wait and retry */ + pr_info("%s: PowerButton is not released.\n", __func__); + } + mdelay(1000); + } +} + +#define REBOOT_MODE_PREFIX 0x12345670 +#define REBOOT_MODE_NONE 0 +#define REBOOT_MODE_DOWNLOAD 1 +#define REBOOT_MODE_UPLOAD 2 +#define REBOOT_MODE_CHARGING 3 +#define REBOOT_MODE_RECOVERY 4 +#define REBOOT_MODE_FOTA 5 +#define REBOOT_MODE_FOTA_BL 6 /* update bootloader */ +#define REBOOT_MODE_SECURE 7 /* image secure check fail */ + +#define REBOOT_SET_PREFIX 0xabc00000 +#define REBOOT_SET_DEBUG 0x000d0000 +#define REBOOT_SET_SWSEL 0x000e0000 +#define REBOOT_SET_SUD 0x000f0000 + +static void sec_reboot(char str, const char *cmd) +{ + local_irq_disable(); + + pr_emerg("%s (%d, %s)\n", __func__, str, cmd ? cmd : "(null)"); + + writel(0x12345678, S5P_INFORM2); /* Don't enter lpm mode */ + + if (!cmd) { + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_NONE, S5P_INFORM3); + } else { + unsigned long value; + if (!strcmp(cmd, "fota")) + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_FOTA, + S5P_INFORM3); + else if (!strcmp(cmd, "fota_bl")) + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_FOTA_BL, + S5P_INFORM3); + else if (!strcmp(cmd, "recovery")) + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_RECOVERY, + S5P_INFORM3); + else if (!strcmp(cmd, "download")) + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_DOWNLOAD, + S5P_INFORM3); + else if (!strcmp(cmd, "upload")) + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_UPLOAD, + S5P_INFORM3); + else if (!strcmp(cmd, "secure")) + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_SECURE, + S5P_INFORM3); + else if (!strncmp(cmd, "debug", 5) + && !kstrtoul(cmd + 5, 0, &value)) + writel(REBOOT_SET_PREFIX | REBOOT_SET_DEBUG | value, + S5P_INFORM3); + else if (!strncmp(cmd, "swsel", 5) + && !kstrtoul(cmd + 5, 0, &value)) + writel(REBOOT_SET_PREFIX | REBOOT_SET_SWSEL | value, + S5P_INFORM3); + else if (!strncmp(cmd, "sud", 3) + && !kstrtoul(cmd + 3, 0, &value)) + writel(REBOOT_SET_PREFIX | REBOOT_SET_SUD | value, + S5P_INFORM3); + else if (!strncmp(cmd, "emergency", 9)) + writel(0, S5P_INFORM3); + else + writel(REBOOT_MODE_PREFIX | REBOOT_MODE_NONE, + S5P_INFORM3); + } + + flush_cache_all(); + outer_flush_all(); + + exynos4_restart(0, 0); + + pr_emerg("%s: waiting for reboot\n", __func__); + while (1) + ; +} + +#ifdef CONFIG_OF +static struct sec_reboot_platform_data *sec_reboot_parse_dt(struct device *dev) +{ + struct device_node *np = dev->of_node; + struct sec_reboot_platform_data *pdata; + + if (!np) + return NULL; + + pdata = devm_kzalloc(dev, sizeof(struct sec_reboot_platform_data), + GFP_KERNEL); + if (!pdata) + return ERR_PTR(ENOMEM); + + pdata->power_button = of_get_named_gpio(np, "power-gpio", 0); + if (!gpio_is_valid(pdata->power_button)) { + dev_err(dev, "invalied power-gpio\n"); + return NULL; + } + + return pdata; +} +#else +static struct sec_reboot_platform_data *sec_reboot_parse_dt(struct device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static int sec_reboot_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + if (pdev->dev.platform_data) + g_pdata = dev_get_platdata(dev); + else if (pdev->dev.of_node) + g_pdata = sec_reboot_parse_dt(dev); + else + g_pdata = NULL; + + if (!g_pdata) { + dev_err(&pdev->dev, "failed to get platform data\n"); + return -EINVAL; + } + + g_pdata->dev = &pdev->dev; + + /* Set machine specific functions */ + pm_power_off = sec_power_off; + arm_pm_restart = sec_reboot; + + return 0; +} + +static int sec_reboot_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct of_device_id sec_reboot_of_match[] = { + { .compatible = "samsung,sec-reboot", }, + { }, +}; + +static struct platform_driver sec_reboot_driver = { + .probe = sec_reboot_probe, + .remove = sec_reboot_remove, + .driver = { + .name = "sec-reboot", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(sec_reboot_of_match), + } +}; + +module_platform_driver(sec_reboot_driver); |