/*
- * DB3 Platform driver for AP bridge control interface.
+ * DB3 Platform driver to enable Unipro link.
  *
  * Copyright 2014-2015 Google Inc.
  * Copyright 2014-2015 Linaro Ltd.
 #include <linux/delay.h>
 #include <linux/platform_device.h>
 #include <linux/gpio.h>
+#include <linux/clk.h>
 #include <linux/of_platform.h>
 #include <linux/of_gpio.h>
 #include <linux/of_irq.h>
 #include <linux/regulator/consumer.h>
 #include <linux/pinctrl/consumer.h>
 
-enum apb_state {
-       APB_STATE_OFF,
-       APB_STATE_ACTIVE,
-       APB_STATE_STANDBY,
-};
-
-/*
- * Control GPIO signals to and from AP <=> AP Bridges
- */
-struct apb_ctrl_gpios {
-       int wake_detect; /* bi-dir, maps to WAKE_MOD and WAKE_FRAME signals */
-       int reset;
-       int boot_ret;
-       int pwroff;
-       int wake_in;
-       int wake_out;
-       int pwrdn;
-};
-
-struct apb_ctrl_drvdata {
-       struct apb_ctrl_gpios ctrl;
+struct db3_platform_drvdata {
+       /* Control GPIO signals to and from AP <=> SVC */
+       int svc_reset_gpio;
+       bool is_reset_act_hi;
+       int svc_sysboot_gpio;
 
-       unsigned int wake_detect_irq;
-       enum apb_state state;
-
-       struct regulator *vcore;
-       struct regulator *vio;
+       unsigned int svc_refclk_req;
+       struct clk *svc_ref_clk;
 
        struct pinctrl *pinctrl;
        struct pinctrl_state *pin_default;
 
-       /* To protect concurrent access of GPIO registers, need protection */
-       spinlock_t lock;
+       int num_apbs;
 };
 
-static inline void assert_wake(unsigned int wake_detect)
-{
-       gpio_set_value(wake_detect, 1);
-}
-
-static inline void deassert_wake(unsigned int wake_detect)
-{
-       gpio_set_value(wake_detect, 0);
-}
-
-static irqreturn_t apb_ctrl_wake_detect_irq(int irq, void *devid)
+static inline void svc_reset_onoff(unsigned int gpio, bool onoff)
 {
-       struct apb_ctrl_drvdata *apb_data = devid;
-       unsigned long flags;
-
-       /*
-        * TODO:
-        * Since currently SoC GPIOs are being used we are safe here
-        * But ideally we should create a workqueue and process the control
-        * signals, especially when we start using GPIOs over slow
-        * buses like I2C.
-        */
-       if (!gpio_is_valid(apb_data->ctrl.wake_detect) &&
-                       !gpio_is_valid(apb_data->ctrl.reset))
-               return IRQ_HANDLED; /* Should it be IRQ_NONE ?? */
-
-       spin_lock_irqsave(&apb_data->lock, flags);
-
-       if (apb_data->state != APB_STATE_ACTIVE) {
-               /* Bring bridge out of reset on this event */
-               gpio_set_value(apb_data->ctrl.reset, 0);
-               apb_data->state = APB_STATE_ACTIVE;
-       } else {
-               /*
-                * Assert Wake_OUT signal to APB
-                * It would resemble WakeDetect module's signal pass-through
-                */
-               /*
-                * We have to generate the pulse, so we may need to schedule
-                * workqueue here.
-                *
-                * Also, since we are using both rising and falling edge for
-                * interrupt trigger, we may not need workqueue. Just pass
-                * through the value to bridge.
-                * Just read GPIO value and pass it to the bridge
-                */
-       }
-
-       spin_unlock_irqrestore(&apb_data->lock, flags);
-
-       return IRQ_HANDLED;
+       gpio_set_value(gpio, onoff);
 }
 
-static void apb_ctrl_cleanup(struct apb_ctrl_drvdata *apb_data)
+static void db3_platform_cleanup(struct db3_platform_drvdata *db3_pdata)
 {
-       if (apb_data->vcore && regulator_is_enabled(apb_data->vcore) > 0)
-               regulator_disable(apb_data->vcore);
-
-       if (apb_data->vio && regulator_is_enabled(apb_data->vio) > 0)
-               regulator_disable(apb_data->vio);
-
        /* As part of exit, put APB back in reset state */
-       if (gpio_is_valid(apb_data->ctrl.reset))
-               gpio_set_value(apb_data->ctrl.reset, 1);
-
-       /* TODO: May have to send an event to SVC about this exit */
+       if (gpio_is_valid(db3_pdata->svc_reset_gpio))
+               svc_reset_onoff(db3_pdata->svc_reset_gpio,
+                               db3_pdata->is_reset_act_hi);
 }
 
-/*
- * Note: Please do not modify the below sequence, as it is as per the spec
- */
-static int apb_ctrl_init_seq(struct device *dev,
-               struct apb_ctrl_drvdata *apb_data)
+static int db3_platform_probe(struct platform_device *pdev)
 {
+       struct db3_platform_drvdata *db3_pdata;
+       struct device *dev = &pdev->dev;
+       struct device_node *np = dev->of_node;
        int ret;
 
-       pinctrl_select_state(apb_data->pinctrl, apb_data->pin_default);
+       db3_pdata = devm_kzalloc(&pdev->dev, sizeof(*db3_pdata), GFP_KERNEL);
+       if (!db3_pdata)
+               return -ENOMEM;
 
-       /* Hold APB in reset state */
-       ret = devm_gpio_request_one(dev, apb_data->ctrl.reset,
-                       GPIOF_OUT_INIT_LOW, "reset");
-       if (ret) {
-               dev_err(dev, "Failed requesting reset gpio %d\n",
-                               apb_data->ctrl.reset);
+       /* setup svc reset gpio */
+       db3_pdata->is_reset_act_hi = of_property_read_bool(np, "svc,reset-active-high");
+       db3_pdata->svc_reset_gpio = of_get_named_gpio(np, "svc,reset-gpio", 0);
+       if (db3_pdata->svc_reset_gpio < 0) {
+               dev_err(dev, "failed to get reset-gpio\n");
+               ret = -ENODEV;
                return ret;
        }
-
-       /* Enable power to APB */
-       if (apb_data->vcore) {
-               ret = regulator_enable(apb_data->vcore);
-               if (ret) {
-                       dev_err(dev, "failed to enable core regulator\n");
-                       return ret;
-               }
-       }
-
-       if (apb_data->vio) {
-               ret = regulator_enable(apb_data->vio);
-               if (ret) {
-                       dev_err(dev, "failed to enable IO regulator\n");
-                       return ret;
-               }
-       }
-
-       /*
-        * We should be safe here to deassert boot retention signal, as
-        * we are only supporting cold boot as of now.
-        */
-       ret = devm_gpio_request_one(dev, apb_data->ctrl.boot_ret,
-                       GPIOF_OUT_INIT_LOW, "boot retention");
+       ret = devm_gpio_request(dev, db3_pdata->svc_reset_gpio, "svc-reset");
        if (ret) {
-               dev_err(dev, "Failed requesting reset gpio %d\n",
-                               apb_data->ctrl.boot_ret);
+               dev_err(dev, "failed to request svc-reset gpio:%d\n", ret);
                return ret;
        }
-
-       ret = devm_gpio_request(dev, apb_data->ctrl.wake_detect, "wake detect");
-       if (ret)
-               dev_err(dev, "Failed requesting wake_detect gpio %d\n",
-                               apb_data->ctrl.wake_detect);
-
-       return ret;
-}
-
-static int apb_ctrl_get_devtree_data(struct device *dev,
-               struct apb_ctrl_drvdata *apb_data)
-{
-       struct device_node *np = dev->of_node;
-
-       /* fetch control signals */
-       apb_data->ctrl.wake_detect = of_get_named_gpio(np, "wake-detect-gpios", 0);
-       if (!gpio_is_valid(apb_data->ctrl.wake_detect)) {
-               dev_err(dev, "failed to get wake detect gpio\n");
-               return apb_data->ctrl.wake_detect;
-       }
-
-       apb_data->ctrl.reset = of_get_named_gpio(np, "reset-gpios", 0);
-       if (!gpio_is_valid(apb_data->ctrl.reset)) {
-               dev_err(dev, "failed to get reset gpio\n");
-               return apb_data->ctrl.reset;
+       ret = gpio_direction_output(db3_pdata->svc_reset_gpio, db3_pdata->is_reset_act_hi);
+       if (ret) {
+               dev_err(dev, "failed to set svc-reset gpio dir:%d\n", ret);
+               return ret;
        }
 
-       apb_data->ctrl.boot_ret = of_get_named_gpio(np, "boot-ret-gpios", 0);
-       if (!gpio_is_valid(apb_data->ctrl.boot_ret)) {
-               dev_err(dev, "failed to get boot retention gpio\n");
-               return apb_data->ctrl.boot_ret;
+       db3_pdata->svc_sysboot_gpio = of_get_named_gpio(np, "svc,sysboot-gpio", 0);
+       if (db3_pdata->svc_sysboot_gpio < 0) {
+               dev_err(dev, "failed to get sysboot gpio\n");
+               ret = -ENODEV;
+               return ret;
        }
-
-       /* It's not mandatory to support power management interface */
-       apb_data->ctrl.pwroff = of_get_named_gpio(np, "pwr-off-gpios", 0);
-       if (!gpio_is_valid(apb_data->ctrl.pwroff))
-               dev_info(dev, "failed to get power off gpio\n");
-
-       apb_data->ctrl.pwrdn = of_get_named_gpio(np, "pwr-down-gpios", 0);
-       if (!gpio_is_valid(apb_data->ctrl.pwrdn))
-               dev_info(dev, "failed to get power down gpio\n");
-
-       /* Regulators are optional, as we may have fixed supply coming in */
-       apb_data->vcore = devm_regulator_get(dev, "vcore");
-       if (IS_ERR_OR_NULL(apb_data->vcore)) {
-               dev_info(dev, "no core regulator found\n");
-               apb_data->vcore = NULL;
+       ret = devm_gpio_request(dev, db3_pdata->svc_sysboot_gpio, "sysboot0");
+       if (ret) {
+               dev_err(dev, "failed to request sysboot0 gpio:%d\n", ret);
+               return ret;
        }
-
-       apb_data->vio = devm_regulator_get(dev, "vio");
-       if (IS_ERR_OR_NULL(apb_data->vio)) {
-               dev_info(dev, "no IO regulator found\n");
-               apb_data->vio = NULL;
+       ret = gpio_direction_output(db3_pdata->svc_sysboot_gpio, 0);
+       if (ret) {
+               dev_err(dev, "failed to set svc-reset gpio dir:%d\n", ret);
+               return ret;
        }
 
-       apb_data->pinctrl = devm_pinctrl_get(dev);
-       if (IS_ERR(apb_data->pinctrl)) {
-               dev_err(dev, "could not get pinctrl handle\n");
-               return PTR_ERR(apb_data->pinctrl);
+       /* setup the clock request gpio first */
+       db3_pdata->svc_refclk_req = of_get_named_gpio(np, "svc,refclk-req-gpio", 0);
+       if (db3_pdata->svc_refclk_req < 0) {
+               dev_err(dev, "failed to get svc clock-req gpio\n");
+               return -ENODEV;
        }
-       apb_data->pin_default = pinctrl_lookup_state(apb_data->pinctrl, "default");
-       if (IS_ERR(apb_data->pin_default)) {
-               dev_err(dev, "could not get default pin state\n");
-               return PTR_ERR(apb_data->pin_default);
+       ret = devm_gpio_request(dev, db3_pdata->svc_refclk_req, "svc-clk-req");
+       if (ret) {
+               dev_err(dev, "failed to request svc-clk-req gpio: %d\n", ret);
+               return ret;
        }
-
-       return 0;
-}
-
-static int apb_ctrl_probe(struct platform_device *pdev)
-{
-       struct device *dev = &pdev->dev;
-       struct apb_ctrl_drvdata *apb_data;
-       int ret;
-
-       apb_data = devm_kzalloc(&pdev->dev, sizeof(*apb_data), GFP_KERNEL);
-       if (!apb_data)
-               return -ENOMEM;
-
-       ret = apb_ctrl_get_devtree_data(dev, apb_data);
+       ret = gpio_direction_input(db3_pdata->svc_refclk_req);
        if (ret) {
-               dev_err(dev, "failed to get devicetree data %d\n", ret);
+               dev_err(dev, "failed to set svc-clk-req gpio dir :%d\n", ret);
                return ret;
        }
 
-       ret = apb_ctrl_init_seq(dev, apb_data);
+       /* setup refclk2 to follow the pin */
+       db3_pdata->svc_ref_clk = devm_clk_get(dev, "svc_ref_clk");
+       if (IS_ERR(db3_pdata->svc_ref_clk)) {
+               ret = PTR_ERR(db3_pdata->svc_ref_clk);
+               dev_err(dev, "failed to get svc_ref_clk: %d\n", ret);
+               return ret;
+       }
+       ret = clk_prepare_enable(db3_pdata->svc_ref_clk);
        if (ret) {
-               dev_err(dev, "failed to set init state of control signal %d\n",
-                               ret);
-               goto exit;
+               dev_err(dev, "failed to enable svc_ref_clk: %d\n", ret);
+               return ret;
        }
 
-       platform_set_drvdata(pdev, apb_data);
-
-       apb_data->state = APB_STATE_OFF;
-       /*
-        * Assert AP module detect signal by pulling wake_detect low
-        */
-       deassert_wake(apb_data->ctrl.wake_detect);
+       platform_set_drvdata(pdev, db3_pdata);
 
-       /*
-        * In order to receive an interrupt, the GPIO must be set to input mode
-        *
-        * As per WDM spec, for the cold boot, the wake pulse must be
-        * >= 5000 usec, but at this stage it is power up sequence,
-        * so we always treat it as cold boot.
-        */
-       gpio_direction_input(apb_data->ctrl.wake_detect);
+       db3_pdata->num_apbs = of_get_child_count(np);
+       dev_dbg(dev, "Number of APB's available - %d\n", db3_pdata->num_apbs);
 
-       ret = devm_request_irq(dev, gpio_to_irq(apb_data->ctrl.wake_detect),
-                       apb_ctrl_wake_detect_irq,
-                       IRQF_TRIGGER_RISING,
-                       "wake detect", apb_data);
-       if (ret) {
-               dev_err(&pdev->dev, "failed to request wake detect IRQ\n");
-               goto exit;
-       }
+       /* bring SVC out of reset */
+       svc_reset_onoff(db3_pdata->svc_reset_gpio, !db3_pdata->is_reset_act_hi);
 
-       /*
-        * Interrupt handling for WAKE_IN (from bridge) signal is required
-        *
-        * Assumption here is, AP already would have woken up and in the
-        * WAKE_IN/WAKE_FRAME event from bridge, as AP would pass-through event
-        * to SVC.
-        *
-        * Not sure anything else needs to take care here.
-        */
-       dev_info(dev, "Device registered successfully \n");
-       return 0;
+       /* probe all childs here */
+       ret = of_platform_populate(np, NULL, NULL, dev);
+       if (ret)
+               dev_err(dev, "no child node found\n");
 
-exit:
-       apb_ctrl_cleanup(apb_data);
+       dev_info(dev, "Device registered successfully\n");
        return ret;
 }
 
-static int apb_ctrl_remove(struct platform_device *pdev)
+static int db3_platform_remove(struct platform_device *pdev)
 {
-       struct apb_ctrl_drvdata *apb_data = platform_get_drvdata(pdev);
+       struct db3_platform_drvdata *db3_pdata = platform_get_drvdata(pdev);
 
-       if (apb_data)
-               apb_ctrl_cleanup(apb_data);
+       if (db3_pdata)
+               db3_platform_cleanup(db3_pdata);
 
        platform_set_drvdata(pdev, NULL);
 
        return 0;
 }
 
-static int apb_ctrl_suspend(struct device *dev)
+static int db3_platform_suspend(struct device *dev)
 {
        /*
         * If timing profile premits, we may shutdown bridge
        return 0;
 }
 
-static int apb_ctrl_resume(struct device *dev)
+static int db3_platform_resume(struct device *dev)
 {
        /*
         * Atleast for ES2 we have to meet the delay requirement between
        return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(apb_ctrl_pm_ops, apb_ctrl_suspend, apb_ctrl_resume);
+static SIMPLE_DEV_PM_OPS(db3_platform_pm_ops, db3_platform_suspend, db3_platform_resume);
 
-static struct of_device_id apb_ctrl_of_match[] = {
-       { .compatible = "usbffff,2", },
+static struct of_device_id db3_platform_of_match[] = {
+       { .compatible = "google,db3-platform", }, /* Use PID/VID of SVC device */
        { },
 };
-MODULE_DEVICE_TABLE(of, apb_ctrl_of_match);
+MODULE_DEVICE_TABLE(of, db3_platform_of_match);
 
-static struct platform_driver apb_ctrl_device_driver = {
-       .probe          = apb_ctrl_probe,
-       .remove         = apb_ctrl_remove,
+static struct platform_driver db3_platform_device_driver = {
+       .probe          = db3_platform_probe,
+       .remove         = db3_platform_remove,
        .driver         = {
-               .name   = "unipro-APbridge",
-               .pm     = &apb_ctrl_pm_ops,
-               .of_match_table = of_match_ptr(apb_ctrl_of_match),
+               .name   = "db3-platform-ctrl",
+               .pm     = &db3_platform_pm_ops,
+               .of_match_table = of_match_ptr(db3_platform_of_match),
        }
 };
 
-module_platform_driver(apb_ctrl_device_driver);
+module_platform_driver(db3_platform_device_driver);
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>");
-MODULE_DESCRIPTION("AP Bridge Control Driver for DB3 platform");
+MODULE_DESCRIPTION("DB3 Platform Driver");