Commit 0b8af6a9 authored by Vaibhav Hiremath's avatar Vaibhav Hiremath Committed by Greg Kroah-Hartman

greybus: db3(arche)-platform: Remove all APB control code

Current db3(arche)-platform driver was only handling APB control signals,
which is where it all started. But now with completion of DB3/EVT1 bringup
we know that platform driver is more than APB control.

We have to bring SVC up before APB, as SVC supposed to enable
clock to APB's. Note that, in EVT1, AP will have direct control over
APB's clock.
Then we have dependency between APB and USB HUB, where, APB should be
brought up before USB HUB (note that this needs to rootcaused).

This patch cleanup the db3(arche)-platform driver to remove all APB control
code. The idea here is create another driver for APB control
(arche-apb-ctrl.c), which will deal with APB.
And this driver will have generic/common platform specific support,
currently manages SVC resources.

This patch also takes in all the changes from factory branch, discovered
during bringup.
Signed-off-by: default avatarVaibhav Hiremath <vaibhav.hiremath@linaro.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@google.com>
parent 3563ff88
/* /*
* DB3 Platform driver for AP bridge control interface. * DB3 Platform driver to enable Unipro link.
* *
* Copyright 2014-2015 Google Inc. * Copyright 2014-2015 Google Inc.
* Copyright 2014-2015 Linaro Ltd. * Copyright 2014-2015 Linaro Ltd.
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/gpio.h> #include <linux/gpio.h>
#include <linux/clk.h>
#include <linux/of_platform.h> #include <linux/of_platform.h>
#include <linux/of_gpio.h> #include <linux/of_gpio.h>
#include <linux/of_irq.h> #include <linux/of_irq.h>
...@@ -23,304 +24,141 @@ ...@@ -23,304 +24,141 @@
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <linux/pinctrl/consumer.h> #include <linux/pinctrl/consumer.h>
enum apb_state { struct db3_platform_drvdata {
APB_STATE_OFF, /* Control GPIO signals to and from AP <=> SVC */
APB_STATE_ACTIVE, int svc_reset_gpio;
APB_STATE_STANDBY, bool is_reset_act_hi;
}; int svc_sysboot_gpio;
/*
* 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;
unsigned int wake_detect_irq; unsigned int svc_refclk_req;
enum apb_state state; struct clk *svc_ref_clk;
struct regulator *vcore;
struct regulator *vio;
struct pinctrl *pinctrl; struct pinctrl *pinctrl;
struct pinctrl_state *pin_default; struct pinctrl_state *pin_default;
/* To protect concurrent access of GPIO registers, need protection */ int num_apbs;
spinlock_t lock;
}; };
static inline void assert_wake(unsigned int wake_detect) static inline void svc_reset_onoff(unsigned int gpio, bool onoff)
{
gpio_set_value(wake_detect, 1);
}
static inline void deassert_wake(unsigned int wake_detect)
{ {
gpio_set_value(wake_detect, 0); gpio_set_value(gpio, onoff);
}
static irqreturn_t apb_ctrl_wake_detect_irq(int irq, void *devid)
{
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;
} }
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 */ /* As part of exit, put APB back in reset state */
if (gpio_is_valid(apb_data->ctrl.reset)) if (gpio_is_valid(db3_pdata->svc_reset_gpio))
gpio_set_value(apb_data->ctrl.reset, 1); svc_reset_onoff(db3_pdata->svc_reset_gpio,
db3_pdata->is_reset_act_hi);
/* TODO: May have to send an event to SVC about this exit */
} }
/* static int db3_platform_probe(struct platform_device *pdev)
* 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)
{ {
struct db3_platform_drvdata *db3_pdata;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int ret; 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 */ /* setup svc reset gpio */
ret = devm_gpio_request_one(dev, apb_data->ctrl.reset, db3_pdata->is_reset_act_hi = of_property_read_bool(np, "svc,reset-active-high");
GPIOF_OUT_INIT_LOW, "reset"); db3_pdata->svc_reset_gpio = of_get_named_gpio(np, "svc,reset-gpio", 0);
if (ret) { if (db3_pdata->svc_reset_gpio < 0) {
dev_err(dev, "Failed requesting reset gpio %d\n", dev_err(dev, "failed to get reset-gpio\n");
apb_data->ctrl.reset); ret = -ENODEV;
return ret; return ret;
} }
ret = devm_gpio_request(dev, db3_pdata->svc_reset_gpio, "svc-reset");
/* Enable power to APB */
if (apb_data->vcore) {
ret = regulator_enable(apb_data->vcore);
if (ret) { if (ret) {
dev_err(dev, "failed to enable core regulator\n"); dev_err(dev, "failed to request svc-reset gpio:%d\n", ret);
return ret; return ret;
} }
} ret = gpio_direction_output(db3_pdata->svc_reset_gpio, db3_pdata->is_reset_act_hi);
if (apb_data->vio) {
ret = regulator_enable(apb_data->vio);
if (ret) { if (ret) {
dev_err(dev, "failed to enable IO regulator\n"); dev_err(dev, "failed to set svc-reset gpio dir:%d\n", ret);
return ret; return ret;
} }
}
/* db3_pdata->svc_sysboot_gpio = of_get_named_gpio(np, "svc,sysboot-gpio", 0);
* We should be safe here to deassert boot retention signal, as if (db3_pdata->svc_sysboot_gpio < 0) {
* we are only supporting cold boot as of now. dev_err(dev, "failed to get sysboot gpio\n");
*/ ret = -ENODEV;
ret = devm_gpio_request_one(dev, apb_data->ctrl.boot_ret,
GPIOF_OUT_INIT_LOW, "boot retention");
if (ret) {
dev_err(dev, "Failed requesting reset gpio %d\n",
apb_data->ctrl.boot_ret);
return ret; return ret;
} }
ret = devm_gpio_request(dev, db3_pdata->svc_sysboot_gpio, "sysboot0");
ret = devm_gpio_request(dev, apb_data->ctrl.wake_detect, "wake detect"); if (ret) {
if (ret) dev_err(dev, "failed to request sysboot0 gpio:%d\n", ret);
dev_err(dev, "Failed requesting wake_detect gpio %d\n",
apb_data->ctrl.wake_detect);
return ret; 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;
}
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;
} }
ret = gpio_direction_output(db3_pdata->svc_sysboot_gpio, 0);
/* It's not mandatory to support power management interface */ if (ret) {
apb_data->ctrl.pwroff = of_get_named_gpio(np, "pwr-off-gpios", 0); dev_err(dev, "failed to set svc-reset gpio dir:%d\n", ret);
if (!gpio_is_valid(apb_data->ctrl.pwroff)) return ret;
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;
}
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;
} }
apb_data->pinctrl = devm_pinctrl_get(dev); /* setup the clock request gpio first */
if (IS_ERR(apb_data->pinctrl)) { db3_pdata->svc_refclk_req = of_get_named_gpio(np, "svc,refclk-req-gpio", 0);
dev_err(dev, "could not get pinctrl handle\n"); if (db3_pdata->svc_refclk_req < 0) {
return PTR_ERR(apb_data->pinctrl); dev_err(dev, "failed to get svc clock-req gpio\n");
return -ENODEV;
} }
apb_data->pin_default = pinctrl_lookup_state(apb_data->pinctrl, "default"); ret = devm_gpio_request(dev, db3_pdata->svc_refclk_req, "svc-clk-req");
if (IS_ERR(apb_data->pin_default)) { if (ret) {
dev_err(dev, "could not get default pin state\n"); dev_err(dev, "failed to request svc-clk-req gpio: %d\n", ret);
return PTR_ERR(apb_data->pin_default); return ret;
} }
ret = gpio_direction_input(db3_pdata->svc_refclk_req);
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);
if (ret) { 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; 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) { if (ret) {
dev_err(dev, "failed to set init state of control signal %d\n", dev_err(dev, "failed to enable svc_ref_clk: %d\n", ret);
ret); return ret;
goto exit;
} }
platform_set_drvdata(pdev, apb_data); platform_set_drvdata(pdev, db3_pdata);
apb_data->state = APB_STATE_OFF;
/*
* Assert AP module detect signal by pulling wake_detect low
*/
deassert_wake(apb_data->ctrl.wake_detect);
/* db3_pdata->num_apbs = of_get_child_count(np);
* In order to receive an interrupt, the GPIO must be set to input mode dev_dbg(dev, "Number of APB's available - %d\n", db3_pdata->num_apbs);
*
* 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);
ret = devm_request_irq(dev, gpio_to_irq(apb_data->ctrl.wake_detect), /* bring SVC out of reset */
apb_ctrl_wake_detect_irq, svc_reset_onoff(db3_pdata->svc_reset_gpio, !db3_pdata->is_reset_act_hi);
IRQF_TRIGGER_RISING,
"wake detect", apb_data);
if (ret) {
dev_err(&pdev->dev, "failed to request wake detect IRQ\n");
goto exit;
}
/* /* probe all childs here */
* Interrupt handling for WAKE_IN (from bridge) signal is required ret = of_platform_populate(np, NULL, NULL, dev);
* if (ret)
* Assumption here is, AP already would have woken up and in the dev_err(dev, "no child node found\n");
* 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;
exit: dev_info(dev, "Device registered successfully\n");
apb_ctrl_cleanup(apb_data);
return ret; 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) if (db3_pdata)
apb_ctrl_cleanup(apb_data); db3_platform_cleanup(db3_pdata);
platform_set_drvdata(pdev, NULL); platform_set_drvdata(pdev, NULL);
return 0; 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 * If timing profile premits, we may shutdown bridge
...@@ -334,7 +172,7 @@ static int apb_ctrl_suspend(struct device *dev) ...@@ -334,7 +172,7 @@ static int apb_ctrl_suspend(struct device *dev)
return 0; 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 * Atleast for ES2 we have to meet the delay requirement between
...@@ -348,26 +186,26 @@ static int apb_ctrl_resume(struct device *dev) ...@@ -348,26 +186,26 @@ static int apb_ctrl_resume(struct device *dev)
return 0; 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[] = { static struct of_device_id db3_platform_of_match[] = {
{ .compatible = "usbffff,2", }, { .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 = { static struct platform_driver db3_platform_device_driver = {
.probe = apb_ctrl_probe, .probe = db3_platform_probe,
.remove = apb_ctrl_remove, .remove = db3_platform_remove,
.driver = { .driver = {
.name = "unipro-APbridge", .name = "db3-platform-ctrl",
.pm = &apb_ctrl_pm_ops, .pm = &db3_platform_pm_ops,
.of_match_table = of_match_ptr(apb_ctrl_of_match), .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_LICENSE("GPL");
MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>"); MODULE_AUTHOR("Vaibhav Hiremath <vaibhav.hiremath@linaro.org>");
MODULE_DESCRIPTION("AP Bridge Control Driver for DB3 platform"); MODULE_DESCRIPTION("DB3 Platform Driver");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment