root/drivers/staging/greybus/arche-platform.c
// SPDX-License-Identifier: GPL-2.0
/*
 * Arche Platform driver to enable Unipro link.
 *
 * Copyright 2014-2015 Google Inc.
 * Copyright 2014-2015 Linaro Ltd.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <linux/pm.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/suspend.h>
#include <linux/time.h>
#include <linux/greybus.h>
#include <linux/of.h>
#include "arche_platform.h"

#if IS_ENABLED(CONFIG_USB_HSIC_USB3613)
#include <linux/usb/usb3613.h>
#else
static inline int usb3613_hub_mode_ctrl(bool unused)
{
        return 0;
}
#endif

#define WD_COLDBOOT_PULSE_WIDTH_MS      30

enum svc_wakedetect_state {
        WD_STATE_IDLE,                  /* Default state = pulled high/low */
        WD_STATE_BOOT_INIT,             /* WD = falling edge (low) */
        WD_STATE_COLDBOOT_TRIG,         /* WD = rising edge (high), > 30msec */
        WD_STATE_STANDBYBOOT_TRIG,      /* As of now not used ?? */
        WD_STATE_COLDBOOT_START,        /* Cold boot process started */
        WD_STATE_STANDBYBOOT_START,     /* Not used */
};

struct arche_platform_drvdata {
        /* Control GPIO signals to and from AP <=> SVC */
        struct gpio_desc *svc_reset;
        bool is_reset_act_hi;
        struct gpio_desc *svc_sysboot;
        struct gpio_desc *wake_detect; /* bi-dir,maps to WAKE_MOD & WAKE_FRAME signals */

        enum arche_platform_state state;

        struct gpio_desc *svc_refclk_req;
        struct clk *svc_ref_clk;

        struct pinctrl *pinctrl;
        struct pinctrl_state *pin_default;

        int num_apbs;

        enum svc_wakedetect_state wake_detect_state;
        int wake_detect_irq;
        spinlock_t wake_lock;                   /* Protect wake_detect_state */
        struct mutex platform_state_mutex;      /* Protect state */
        unsigned long wake_detect_start;
        struct notifier_block pm_notifier;

        struct device *dev;
};

/* Requires calling context to hold arche_pdata->platform_state_mutex */
static void arche_platform_set_state(struct arche_platform_drvdata *arche_pdata,
                                     enum arche_platform_state state)
{
        arche_pdata->state = state;
}

/* Requires arche_pdata->wake_lock is held by calling context */
static void arche_platform_set_wake_detect_state(struct arche_platform_drvdata *arche_pdata,
                                                 enum svc_wakedetect_state state)
{
        arche_pdata->wake_detect_state = state;
}

static inline void svc_reset_onoff(struct gpio_desc *gpio, bool onoff)
{
        gpiod_set_raw_value(gpio, onoff);
}

static int apb_cold_boot(struct device *dev, void *data)
{
        int ret;

        ret = apb_ctrl_coldboot(dev);
        if (ret)
                dev_warn(dev, "failed to coldboot\n");

        /*Child nodes are independent, so do not exit coldboot operation */
        return 0;
}

static int apb_poweroff(struct device *dev, void *data)
{
        apb_ctrl_poweroff(dev);

        /* Enable HUB3613 into HUB mode. */
        if (usb3613_hub_mode_ctrl(false))
                dev_warn(dev, "failed to control hub device\n");

        return 0;
}

static void arche_platform_wd_irq_en(struct arche_platform_drvdata *arche_pdata)
{
        /* Enable interrupt here, to read event back from SVC */
        enable_irq(arche_pdata->wake_detect_irq);
}

static irqreturn_t arche_platform_wd_irq_thread(int irq, void *devid)
{
        struct arche_platform_drvdata *arche_pdata = devid;
        unsigned long flags;

        spin_lock_irqsave(&arche_pdata->wake_lock, flags);
        if (arche_pdata->wake_detect_state != WD_STATE_COLDBOOT_TRIG) {
                /* Something is wrong */
                spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
                return IRQ_HANDLED;
        }

        arche_platform_set_wake_detect_state(arche_pdata,
                                             WD_STATE_COLDBOOT_START);
        spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);

        /* It should complete power cycle, so first make sure it is poweroff */
        device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);

        /* Bring APB out of reset: cold boot sequence */
        device_for_each_child(arche_pdata->dev, NULL, apb_cold_boot);

        /* Enable HUB3613 into HUB mode. */
        if (usb3613_hub_mode_ctrl(true))
                dev_warn(arche_pdata->dev, "failed to control hub device\n");

        spin_lock_irqsave(&arche_pdata->wake_lock, flags);
        arche_platform_set_wake_detect_state(arche_pdata, WD_STATE_IDLE);
        spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);

        return IRQ_HANDLED;
}

static irqreturn_t arche_platform_wd_irq(int irq, void *devid)
{
        struct arche_platform_drvdata *arche_pdata = devid;
        unsigned long flags;

        spin_lock_irqsave(&arche_pdata->wake_lock, flags);

        if (gpiod_get_value(arche_pdata->wake_detect)) {
                /* wake/detect rising */

                /*
                 * If wake/detect line goes high after low, within less than
                 * 30msec, then standby boot sequence is initiated, which is not
                 * supported/implemented as of now. So ignore it.
                 */
                if (arche_pdata->wake_detect_state == WD_STATE_BOOT_INIT) {
                        if (time_before(jiffies,
                                        arche_pdata->wake_detect_start +
                                        msecs_to_jiffies(WD_COLDBOOT_PULSE_WIDTH_MS))) {
                                arche_platform_set_wake_detect_state(arche_pdata,
                                                                     WD_STATE_IDLE);
                        } else {
                                /*
                                 * Check we are not in middle of irq thread
                                 * already
                                 */
                                if (arche_pdata->wake_detect_state !=
                                                WD_STATE_COLDBOOT_START) {
                                        arche_platform_set_wake_detect_state(arche_pdata,
                                                                             WD_STATE_COLDBOOT_TRIG);
                                        spin_unlock_irqrestore(&arche_pdata->wake_lock,
                                                               flags);
                                        return IRQ_WAKE_THREAD;
                                }
                        }
                }
        } else {
                /* wake/detect falling */
                if (arche_pdata->wake_detect_state == WD_STATE_IDLE) {
                        arche_pdata->wake_detect_start = jiffies;
                        /*
                         * In the beginning, when wake/detect goes low
                         * (first time), we assume it is meant for coldboot
                         * and set the flag. If wake/detect line stays low
                         * beyond 30msec, then it is coldboot else fallback
                         * to standby boot.
                         */
                        arche_platform_set_wake_detect_state(arche_pdata,
                                                             WD_STATE_BOOT_INIT);
                }
        }

        spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);

        return IRQ_HANDLED;
}

/*
 * Requires arche_pdata->platform_state_mutex to be held
 */
static int
arche_platform_coldboot_seq(struct arche_platform_drvdata *arche_pdata)
{
        int ret;

        if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
                return 0;

        dev_info(arche_pdata->dev, "Booting from cold boot state\n");

        svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);

        gpiod_set_value(arche_pdata->svc_sysboot, 0);
        usleep_range(100, 200);

        ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
        if (ret) {
                dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
                        ret);
                return ret;
        }

        /* bring SVC out of reset */
        svc_reset_onoff(arche_pdata->svc_reset, !arche_pdata->is_reset_act_hi);

        arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_ACTIVE);

        return 0;
}

/*
 * Requires arche_pdata->platform_state_mutex to be held
 */
static int
arche_platform_fw_flashing_seq(struct arche_platform_drvdata *arche_pdata)
{
        int ret;

        if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
                return 0;

        dev_info(arche_pdata->dev, "Switching to FW flashing state\n");

        svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);

        gpiod_set_value(arche_pdata->svc_sysboot, 1);

        usleep_range(100, 200);

        ret = clk_prepare_enable(arche_pdata->svc_ref_clk);
        if (ret) {
                dev_err(arche_pdata->dev, "failed to enable svc_ref_clk: %d\n",
                        ret);
                return ret;
        }

        svc_reset_onoff(arche_pdata->svc_reset, !arche_pdata->is_reset_act_hi);

        arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_FW_FLASHING);

        return 0;
}

/*
 * Requires arche_pdata->platform_state_mutex to be held
 */
static void
arche_platform_poweroff_seq(struct arche_platform_drvdata *arche_pdata)
{
        unsigned long flags;

        if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
                return;

        /* If in fw_flashing mode, then no need to repeate things again */
        if (arche_pdata->state != ARCHE_PLATFORM_STATE_FW_FLASHING) {
                disable_irq(arche_pdata->wake_detect_irq);

                spin_lock_irqsave(&arche_pdata->wake_lock, flags);
                arche_platform_set_wake_detect_state(arche_pdata,
                                                     WD_STATE_IDLE);
                spin_unlock_irqrestore(&arche_pdata->wake_lock, flags);
        }

        clk_disable_unprepare(arche_pdata->svc_ref_clk);

        /* As part of exit, put APB back in reset state */
        svc_reset_onoff(arche_pdata->svc_reset, arche_pdata->is_reset_act_hi);

        arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_OFF);
}

static ssize_t state_store(struct device *dev,
                           struct device_attribute *attr,
                           const char *buf, size_t count)
{
        struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);
        int ret = 0;

        mutex_lock(&arche_pdata->platform_state_mutex);

        if (sysfs_streq(buf, "off")) {
                if (arche_pdata->state == ARCHE_PLATFORM_STATE_OFF)
                        goto exit;

                /*  If SVC goes down, bring down APB's as well */
                device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);

                arche_platform_poweroff_seq(arche_pdata);

        } else if (sysfs_streq(buf, "active")) {
                if (arche_pdata->state == ARCHE_PLATFORM_STATE_ACTIVE)
                        goto exit;

                /* First we want to make sure we power off everything
                 * and then activate back again
                 */
                device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
                arche_platform_poweroff_seq(arche_pdata);

                arche_platform_wd_irq_en(arche_pdata);
                ret = arche_platform_coldboot_seq(arche_pdata);
                if (ret)
                        goto exit;

        } else if (sysfs_streq(buf, "standby")) {
                if (arche_pdata->state == ARCHE_PLATFORM_STATE_STANDBY)
                        goto exit;

                dev_warn(arche_pdata->dev, "standby state not supported\n");
        } else if (sysfs_streq(buf, "fw_flashing")) {
                if (arche_pdata->state == ARCHE_PLATFORM_STATE_FW_FLASHING)
                        goto exit;

                /*
                 * Here we only control SVC.
                 *
                 * In case of FW_FLASHING mode we do not want to control
                 * APBs, as in case of V2, SPI bus is shared between both
                 * the APBs. So let user chose which APB he wants to flash.
                 */
                arche_platform_poweroff_seq(arche_pdata);

                ret = arche_platform_fw_flashing_seq(arche_pdata);
                if (ret)
                        goto exit;
        } else {
                dev_err(arche_pdata->dev, "unknown state\n");
                ret = -EINVAL;
        }

exit:
        mutex_unlock(&arche_pdata->platform_state_mutex);
        return ret ? ret : count;
}

static ssize_t state_show(struct device *dev,
                          struct device_attribute *attr, char *buf)
{
        struct arche_platform_drvdata *arche_pdata = dev_get_drvdata(dev);

        switch (arche_pdata->state) {
        case ARCHE_PLATFORM_STATE_OFF:
                return sprintf(buf, "off\n");
        case ARCHE_PLATFORM_STATE_ACTIVE:
                return sprintf(buf, "active\n");
        case ARCHE_PLATFORM_STATE_STANDBY:
                return sprintf(buf, "standby\n");
        case ARCHE_PLATFORM_STATE_FW_FLASHING:
                return sprintf(buf, "fw_flashing\n");
        default:
                return sprintf(buf, "unknown state\n");
        }
}

static DEVICE_ATTR_RW(state);

static int arche_platform_pm_notifier(struct notifier_block *notifier,
                                      unsigned long pm_event, void *unused)
{
        struct arche_platform_drvdata *arche_pdata =
                container_of(notifier, struct arche_platform_drvdata,
                             pm_notifier);
        int ret = NOTIFY_DONE;

        mutex_lock(&arche_pdata->platform_state_mutex);
        switch (pm_event) {
        case PM_SUSPEND_PREPARE:
                if (arche_pdata->state != ARCHE_PLATFORM_STATE_ACTIVE) {
                        ret = NOTIFY_STOP;
                        break;
                }
                device_for_each_child(arche_pdata->dev, NULL, apb_poweroff);
                arche_platform_poweroff_seq(arche_pdata);
                break;
        case PM_POST_SUSPEND:
                if (arche_pdata->state != ARCHE_PLATFORM_STATE_OFF)
                        break;

                arche_platform_wd_irq_en(arche_pdata);
                arche_platform_coldboot_seq(arche_pdata);
                break;
        default:
                break;
        }
        mutex_unlock(&arche_pdata->platform_state_mutex);

        return ret;
}

static int arche_platform_probe(struct platform_device *pdev)
{
        struct arche_platform_drvdata *arche_pdata;
        struct device *dev = &pdev->dev;
        struct device_node *np = dev->of_node;
        int ret;
        unsigned int flags;

        arche_pdata = devm_kzalloc(&pdev->dev, sizeof(*arche_pdata),
                                   GFP_KERNEL);
        if (!arche_pdata)
                return -ENOMEM;

        /* setup svc reset gpio */
        arche_pdata->is_reset_act_hi = of_property_read_bool(np,
                                                             "svc,reset-active-high");
        if (arche_pdata->is_reset_act_hi)
                flags = GPIOD_OUT_HIGH;
        else
                flags = GPIOD_OUT_LOW;

        arche_pdata->svc_reset = devm_gpiod_get(dev, "svc,reset", flags);
        if (IS_ERR(arche_pdata->svc_reset)) {
                ret = PTR_ERR(arche_pdata->svc_reset);
                dev_err(dev, "failed to request svc-reset GPIO: %d\n", ret);
                return ret;
        }
        arche_platform_set_state(arche_pdata, ARCHE_PLATFORM_STATE_OFF);

        arche_pdata->svc_sysboot = devm_gpiod_get(dev, "svc,sysboot",
                                                  GPIOD_OUT_LOW);
        if (IS_ERR(arche_pdata->svc_sysboot)) {
                ret = PTR_ERR(arche_pdata->svc_sysboot);
                dev_err(dev, "failed to request sysboot0 GPIO: %d\n", ret);
                return ret;
        }

        /* setup the clock request gpio first */
        arche_pdata->svc_refclk_req = devm_gpiod_get(dev, "svc,refclk-req",
                                                     GPIOD_IN);
        if (IS_ERR(arche_pdata->svc_refclk_req)) {
                ret = PTR_ERR(arche_pdata->svc_refclk_req);
                dev_err(dev, "failed to request svc-clk-req GPIO: %d\n", ret);
                return ret;
        }

        /* setup refclk2 to follow the pin */
        arche_pdata->svc_ref_clk = devm_clk_get(dev, "svc_ref_clk");
        if (IS_ERR(arche_pdata->svc_ref_clk)) {
                ret = PTR_ERR(arche_pdata->svc_ref_clk);
                dev_err(dev, "failed to get svc_ref_clk: %d\n", ret);
                return ret;
        }

        platform_set_drvdata(pdev, arche_pdata);

        arche_pdata->num_apbs = of_get_child_count(np);
        dev_dbg(dev, "Number of APB's available - %d\n", arche_pdata->num_apbs);

        arche_pdata->wake_detect = devm_gpiod_get(dev, "svc,wake-detect",
                                                  GPIOD_IN);
        if (IS_ERR(arche_pdata->wake_detect)) {
                ret = PTR_ERR(arche_pdata->wake_detect);
                dev_err(dev, "Failed requesting wake_detect GPIO: %d\n", ret);
                return ret;
        }

        arche_platform_set_wake_detect_state(arche_pdata, WD_STATE_IDLE);

        arche_pdata->dev = &pdev->dev;

        spin_lock_init(&arche_pdata->wake_lock);
        mutex_init(&arche_pdata->platform_state_mutex);
        arche_pdata->wake_detect_irq =
                gpiod_to_irq(arche_pdata->wake_detect);

        ret = devm_request_threaded_irq(dev, arche_pdata->wake_detect_irq,
                                        arche_platform_wd_irq,
                                        arche_platform_wd_irq_thread,
                                        IRQF_TRIGGER_FALLING |
                                        IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                                        dev_name(dev), arche_pdata);
        if (ret) {
                dev_err(dev, "failed to request wake detect IRQ %d\n", ret);
                return ret;
        }
        disable_irq(arche_pdata->wake_detect_irq);

        ret = device_create_file(dev, &dev_attr_state);
        if (ret) {
                dev_err(dev, "failed to create state file in sysfs\n");
                return ret;
        }

        ret = of_platform_populate(np, NULL, NULL, dev);
        if (ret) {
                dev_err(dev, "failed to populate child nodes %d\n", ret);
                goto err_device_remove;
        }

        arche_pdata->pm_notifier.notifier_call = arche_platform_pm_notifier;
        ret = register_pm_notifier(&arche_pdata->pm_notifier);
        if (ret) {
                dev_err(dev, "failed to register pm notifier %d\n", ret);
                goto err_depopulate;
        }

        /* Explicitly power off if requested */
        if (!of_property_read_bool(pdev->dev.of_node, "arche,init-off")) {
                mutex_lock(&arche_pdata->platform_state_mutex);
                ret = arche_platform_coldboot_seq(arche_pdata);
                if (ret) {
                        mutex_unlock(&arche_pdata->platform_state_mutex);
                        dev_err(dev, "Failed to cold boot svc %d\n", ret);
                        goto err_unregister_pm_notifier;
                }
                arche_platform_wd_irq_en(arche_pdata);
                mutex_unlock(&arche_pdata->platform_state_mutex);
        }

        dev_info(dev, "Device registered successfully\n");
        return 0;

err_unregister_pm_notifier:
        unregister_pm_notifier(&arche_pdata->pm_notifier);
err_depopulate:
        of_platform_depopulate(dev);
err_device_remove:
        device_remove_file(&pdev->dev, &dev_attr_state);
        return ret;
}

static void arche_platform_remove(struct platform_device *pdev)
{
        struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);

        unregister_pm_notifier(&arche_pdata->pm_notifier);
        device_remove_file(&pdev->dev, &dev_attr_state);
        of_platform_depopulate(&pdev->dev);
        arche_platform_poweroff_seq(arche_pdata);

        if (usb3613_hub_mode_ctrl(false))
                dev_warn(arche_pdata->dev, "failed to control hub device\n");
}

static __maybe_unused int arche_platform_suspend(struct device *dev)
{
        /*
         * If timing profile permits, we may shutdown bridge
         * completely
         *
         * TODO: define shutdown sequence
         *
         * Also, need to make sure we meet precondition for unipro suspend
         * Precondition: Definition ???
         */
        return 0;
}

static __maybe_unused int arche_platform_resume(struct device *dev)
{
        /*
         * At least for ES2 we have to meet the delay requirement between
         * unipro switch and AP bridge init, depending on whether bridge is in
         * OFF state or standby state.
         *
         * Based on whether bridge is in standby or OFF state we may have to
         * assert multiple signals. Please refer to WDM spec, for more info.
         *
         */
        return 0;
}

static void arche_platform_shutdown(struct platform_device *pdev)
{
        struct arche_platform_drvdata *arche_pdata = platform_get_drvdata(pdev);

        arche_platform_poweroff_seq(arche_pdata);

        usb3613_hub_mode_ctrl(false);
}

static SIMPLE_DEV_PM_OPS(arche_platform_pm_ops,
                        arche_platform_suspend,
                        arche_platform_resume);

static const struct of_device_id arche_platform_of_match[] = {
        /* Use PID/VID of SVC device */
        { .compatible = "google,arche-platform", },
        { },
};
MODULE_DEVICE_TABLE(of, arche_platform_of_match);

static struct platform_driver arche_platform_device_driver = {
        .probe          = arche_platform_probe,
        .remove         = arche_platform_remove,
        .shutdown       = arche_platform_shutdown,
        .driver         = {
                .name   = "arche-platform-ctrl",
                .pm     = &arche_platform_pm_ops,
                .of_match_table = arche_platform_of_match,
        }
};

static int __init arche_init(void)
{
        int retval;

        retval = platform_driver_register(&arche_platform_device_driver);
        if (retval)
                return retval;

        retval = arche_apb_init();
        if (retval)
                platform_driver_unregister(&arche_platform_device_driver);

        return retval;
}
module_init(arche_init);

static void __exit arche_exit(void)
{
        arche_apb_exit();
        platform_driver_unregister(&arche_platform_device_driver);
}
module_exit(arche_exit);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>");
MODULE_DESCRIPTION("Arche Platform Driver");