/* Copyright (c) 2014, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #define CLOCK_CONTROL_REG 0x00 #define BUS_SMCBC_REG 0x04 #define PSCBC_BUS_REG 0x0C #define PSCBC_GENI_REG 0x10 #define STANDBY_MODE 0x2 #define ACTIVE_MODE 0x1 #define ASYNC_SW_CLK_EN 0x2 struct msm_spss_dev_t { void __iomem *base; struct clk *clk; }; static struct msm_spss_dev_t msm_spss_dev; static int msm_spss_probe(struct platform_device *pdev) { struct resource *res; int rc = 0; msm_spss_dev.clk = clk_get(&pdev->dev, "iface_clk"); if (IS_ERR(msm_spss_dev.clk)) { rc = PTR_ERR(msm_spss_dev.clk); dev_err(&pdev->dev, "could not get ahb clk %d\n", rc); goto err_clk_get; } res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "base"); if (!res) { dev_err(&pdev->dev, "missing memory resource\n"); rc = -EINVAL; goto err_res; } msm_spss_dev.base = ioremap(res->start, resource_size(res)); if (!msm_spss_dev.base) { dev_err(&pdev->dev, "ioremap failed\n"); rc = -ENOMEM; goto err_ioremap; } rc = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); if (rc) { dev_err(&pdev->dev, "of_platform_populate failed %d\n", rc); goto err_of_plat_pop; } rc = clk_prepare_enable(msm_spss_dev.clk); if (rc) { dev_err(&pdev->dev, "ahb clk enable failed %d\n", rc); goto err_clk_en; } writel_relaxed(ASYNC_SW_CLK_EN, (msm_spss_dev.base + BUS_SMCBC_REG)); writel_relaxed(ASYNC_SW_CLK_EN, (msm_spss_dev.base + PSCBC_BUS_REG)); writel_relaxed(ASYNC_SW_CLK_EN, (msm_spss_dev.base + PSCBC_GENI_REG)); clk_disable_unprepare(msm_spss_dev.clk); return 0; err_clk_en: err_of_plat_pop: iounmap(msm_spss_dev.base); err_ioremap: err_res: clk_put(msm_spss_dev.clk); err_clk_get: return rc; } static int __exit msm_spss_remove(struct platform_device *pdev) { if (msm_spss_dev.base) iounmap(msm_spss_dev.base); if (msm_spss_dev.clk) clk_put(msm_spss_dev.clk); return 0; } #ifdef CONFIG_PM_SLEEP static int msm_spss_suspend(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); int rc; u32 val; rc = clk_prepare_enable(msm_spss_dev.clk); if (rc) { dev_err(&pdev->dev, "ahb clk enable failed %d\n", rc); return rc; } val = readl_relaxed(msm_spss_dev.base + CLOCK_CONTROL_REG); val &= ~ACTIVE_MODE; val |= STANDBY_MODE; writel_relaxed(val, (msm_spss_dev.base + CLOCK_CONTROL_REG)); wmb(); clk_disable_unprepare(msm_spss_dev.clk); return 0; } static int msm_spss_resume(struct device *dev) { struct platform_device *pdev = to_platform_device(dev); int rc; u32 val; rc = clk_prepare_enable(msm_spss_dev.clk); if (rc) { dev_err(&pdev->dev, "ahb clk enable failed %d\n", rc); return rc; } val = readl_relaxed(msm_spss_dev.base + CLOCK_CONTROL_REG); val &= ~STANDBY_MODE; val |= ACTIVE_MODE; writel_relaxed(val, (msm_spss_dev.base + CLOCK_CONTROL_REG)); wmb(); clk_disable_unprepare(msm_spss_dev.clk); return 0; } #endif /* CONFIG_PM_SLEEP */ static const struct dev_pm_ops msm_spss_dev_pm_ops = { SET_SYSTEM_SLEEP_PM_OPS( msm_spss_suspend, msm_spss_resume ) }; static struct of_device_id msm_spss_match[] = { { .compatible = "qcom,msm-spss", }, {} }; static struct platform_driver msm_spss_driver = { .probe = msm_spss_probe, .remove = msm_spss_remove, .driver = { .name = "msm_spss", .owner = THIS_MODULE, .pm = &msm_spss_dev_pm_ops, .of_match_table = msm_spss_match, }, }; static int __init spss_init(void) { return platform_driver_register(&msm_spss_driver); } static void __exit spss_exit(void) { platform_driver_unregister(&msm_spss_driver); } module_init(spss_init); module_exit(spss_exit); MODULE_LICENSE("GPL v2");