// SPDX-License-Identifier: GPL-2.0 /* * Arche Platform driver to enable Unipro link. * * Copyright 2014-2015 Google Inc. * Copyright 2014-2015 Linaro Ltd. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "arche_platform.h" #if IS_ENABLED(CONFIG_USB_HSIC_USB3613) #include #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_device_remove; } /* 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) { dev_err(dev, "Failed to cold boot svc %d\n", ret); goto err_coldboot; } arche_platform_wd_irq_en(arche_pdata); mutex_unlock(&arche_pdata->platform_state_mutex); } dev_info(dev, "Device registered successfully\n"); return 0; err_coldboot: mutex_unlock(&arche_pdata->platform_state_mutex); err_device_remove: device_remove_file(&pdev->dev, &dev_attr_state); return ret; } static int arche_remove_child(struct device *dev, void *unused) { struct platform_device *pdev = to_platform_device(dev); platform_device_unregister(pdev); return 0; } 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); device_for_each_child(&pdev->dev, NULL, arche_remove_child); 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 premits, we may shutdown bridge * completely * * TODO: 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", }, { }, }; static const struct of_device_id arche_combined_id[] = { /* Use PID/VID of SVC device */ { .compatible = "google,arche-platform", }, { .compatible = "usbffff,2", }, { }, }; MODULE_DEVICE_TABLE(of, arche_combined_id); static struct platform_driver arche_platform_device_driver = { .probe = arche_platform_probe, .remove_new = 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 "); MODULE_DESCRIPTION("Arche Platform Driver");