mirror of
				git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
				synced 2025-09-04 20:19:47 +08:00 
			
		
		
		
	 cc8bbe1a83
			
		
	
	
		cc8bbe1a83
		
	
	
	
	
		
			
			This patch add SMI(Smart Multimedia Interface) driver. This driver is responsible to enable/disable iommu and control the power domain and clocks of each local arbiter. Signed-off-by: Yong Wu <yong.wu@mediatek.com> Tested-by: Philipp Zabel <p.zabel@pengutronix.de> Reviewed-by: Daniel Kurtz <djkurtz@chromium.org> Tested-by: Daniel Kurtz <djkurtz@chromium.org> Signed-off-by: Matthias Brugger <matthias.bgg@gmail.com> Signed-off-by: Joerg Roedel <jroedel@suse.de>
		
			
				
	
	
		
			274 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * Copyright (c) 2015-2016 MediaTek Inc.
 | |
|  * Author: Yong Wu <yong.wu@mediatek.com>
 | |
|  *
 | |
|  * This program is free software; you can redistribute it and/or modify
 | |
|  * it under the terms of the GNU General Public License 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.
 | |
|  */
 | |
| #include <linux/clk.h>
 | |
| #include <linux/component.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/err.h>
 | |
| #include <linux/io.h>
 | |
| #include <linux/of.h>
 | |
| #include <linux/of_platform.h>
 | |
| #include <linux/platform_device.h>
 | |
| #include <linux/pm_runtime.h>
 | |
| #include <soc/mediatek/smi.h>
 | |
| 
 | |
| #define SMI_LARB_MMU_EN		0xf00
 | |
| 
 | |
| struct mtk_smi {
 | |
| 	struct device	*dev;
 | |
| 	struct clk	*clk_apb, *clk_smi;
 | |
| };
 | |
| 
 | |
| struct mtk_smi_larb { /* larb: local arbiter */
 | |
| 	struct mtk_smi  smi;
 | |
| 	void __iomem	*base;
 | |
| 	struct device	*smi_common_dev;
 | |
| 	u32		*mmu;
 | |
| };
 | |
| 
 | |
| static int mtk_smi_enable(const struct mtk_smi *smi)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = pm_runtime_get_sync(smi->dev);
 | |
| 	if (ret < 0)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = clk_prepare_enable(smi->clk_apb);
 | |
| 	if (ret)
 | |
| 		goto err_put_pm;
 | |
| 
 | |
| 	ret = clk_prepare_enable(smi->clk_smi);
 | |
| 	if (ret)
 | |
| 		goto err_disable_apb;
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| err_disable_apb:
 | |
| 	clk_disable_unprepare(smi->clk_apb);
 | |
| err_put_pm:
 | |
| 	pm_runtime_put_sync(smi->dev);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static void mtk_smi_disable(const struct mtk_smi *smi)
 | |
| {
 | |
| 	clk_disable_unprepare(smi->clk_smi);
 | |
| 	clk_disable_unprepare(smi->clk_apb);
 | |
| 	pm_runtime_put_sync(smi->dev);
 | |
| }
 | |
| 
 | |
| int mtk_smi_larb_get(struct device *larbdev)
 | |
| {
 | |
| 	struct mtk_smi_larb *larb = dev_get_drvdata(larbdev);
 | |
| 	struct mtk_smi *common = dev_get_drvdata(larb->smi_common_dev);
 | |
| 	int ret;
 | |
| 
 | |
| 	/* Enable the smi-common's power and clocks */
 | |
| 	ret = mtk_smi_enable(common);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	/* Enable the larb's power and clocks */
 | |
| 	ret = mtk_smi_enable(&larb->smi);
 | |
| 	if (ret) {
 | |
| 		mtk_smi_disable(common);
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	/* Configure the iommu info for this larb */
 | |
| 	writel(*larb->mmu, larb->base + SMI_LARB_MMU_EN);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void mtk_smi_larb_put(struct device *larbdev)
 | |
| {
 | |
| 	struct mtk_smi_larb *larb = dev_get_drvdata(larbdev);
 | |
| 	struct mtk_smi *common = dev_get_drvdata(larb->smi_common_dev);
 | |
| 
 | |
| 	/*
 | |
| 	 * Don't de-configure the iommu info for this larb since there may be
 | |
| 	 * several modules in this larb.
 | |
| 	 * The iommu info will be reset after power off.
 | |
| 	 */
 | |
| 
 | |
| 	mtk_smi_disable(&larb->smi);
 | |
| 	mtk_smi_disable(common);
 | |
| }
 | |
| 
 | |
| static int
 | |
| mtk_smi_larb_bind(struct device *dev, struct device *master, void *data)
 | |
| {
 | |
| 	struct mtk_smi_larb *larb = dev_get_drvdata(dev);
 | |
| 	struct mtk_smi_iommu *smi_iommu = data;
 | |
| 	unsigned int         i;
 | |
| 
 | |
| 	for (i = 0; i < smi_iommu->larb_nr; i++) {
 | |
| 		if (dev == smi_iommu->larb_imu[i].dev) {
 | |
| 			/* The 'mmu' may be updated in iommu-attach/detach. */
 | |
| 			larb->mmu = &smi_iommu->larb_imu[i].mmu;
 | |
| 			return 0;
 | |
| 		}
 | |
| 	}
 | |
| 	return -ENODEV;
 | |
| }
 | |
| 
 | |
| static void
 | |
| mtk_smi_larb_unbind(struct device *dev, struct device *master, void *data)
 | |
| {
 | |
| 	/* Do nothing as the iommu is always enabled. */
 | |
| }
 | |
| 
 | |
| static const struct component_ops mtk_smi_larb_component_ops = {
 | |
| 	.bind = mtk_smi_larb_bind,
 | |
| 	.unbind = mtk_smi_larb_unbind,
 | |
| };
 | |
| 
 | |
| static int mtk_smi_larb_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct mtk_smi_larb *larb;
 | |
| 	struct resource *res;
 | |
| 	struct device *dev = &pdev->dev;
 | |
| 	struct device_node *smi_node;
 | |
| 	struct platform_device *smi_pdev;
 | |
| 
 | |
| 	if (!dev->pm_domain)
 | |
| 		return -EPROBE_DEFER;
 | |
| 
 | |
| 	larb = devm_kzalloc(dev, sizeof(*larb), GFP_KERNEL);
 | |
| 	if (!larb)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 | |
| 	larb->base = devm_ioremap_resource(dev, res);
 | |
| 	if (IS_ERR(larb->base))
 | |
| 		return PTR_ERR(larb->base);
 | |
| 
 | |
| 	larb->smi.clk_apb = devm_clk_get(dev, "apb");
 | |
| 	if (IS_ERR(larb->smi.clk_apb))
 | |
| 		return PTR_ERR(larb->smi.clk_apb);
 | |
| 
 | |
| 	larb->smi.clk_smi = devm_clk_get(dev, "smi");
 | |
| 	if (IS_ERR(larb->smi.clk_smi))
 | |
| 		return PTR_ERR(larb->smi.clk_smi);
 | |
| 	larb->smi.dev = dev;
 | |
| 
 | |
| 	smi_node = of_parse_phandle(dev->of_node, "mediatek,smi", 0);
 | |
| 	if (!smi_node)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	smi_pdev = of_find_device_by_node(smi_node);
 | |
| 	of_node_put(smi_node);
 | |
| 	if (smi_pdev) {
 | |
| 		larb->smi_common_dev = &smi_pdev->dev;
 | |
| 	} else {
 | |
| 		dev_err(dev, "Failed to get the smi_common device\n");
 | |
| 		return -EINVAL;
 | |
| 	}
 | |
| 
 | |
| 	pm_runtime_enable(dev);
 | |
| 	platform_set_drvdata(pdev, larb);
 | |
| 	return component_add(dev, &mtk_smi_larb_component_ops);
 | |
| }
 | |
| 
 | |
| static int mtk_smi_larb_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	pm_runtime_disable(&pdev->dev);
 | |
| 	component_del(&pdev->dev, &mtk_smi_larb_component_ops);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct of_device_id mtk_smi_larb_of_ids[] = {
 | |
| 	{ .compatible = "mediatek,mt8173-smi-larb",},
 | |
| 	{}
 | |
| };
 | |
| 
 | |
| static struct platform_driver mtk_smi_larb_driver = {
 | |
| 	.probe	= mtk_smi_larb_probe,
 | |
| 	.remove = mtk_smi_larb_remove,
 | |
| 	.driver	= {
 | |
| 		.name = "mtk-smi-larb",
 | |
| 		.of_match_table = mtk_smi_larb_of_ids,
 | |
| 	}
 | |
| };
 | |
| 
 | |
| static int mtk_smi_common_probe(struct platform_device *pdev)
 | |
| {
 | |
| 	struct device *dev = &pdev->dev;
 | |
| 	struct mtk_smi *common;
 | |
| 
 | |
| 	if (!dev->pm_domain)
 | |
| 		return -EPROBE_DEFER;
 | |
| 
 | |
| 	common = devm_kzalloc(dev, sizeof(*common), GFP_KERNEL);
 | |
| 	if (!common)
 | |
| 		return -ENOMEM;
 | |
| 	common->dev = dev;
 | |
| 
 | |
| 	common->clk_apb = devm_clk_get(dev, "apb");
 | |
| 	if (IS_ERR(common->clk_apb))
 | |
| 		return PTR_ERR(common->clk_apb);
 | |
| 
 | |
| 	common->clk_smi = devm_clk_get(dev, "smi");
 | |
| 	if (IS_ERR(common->clk_smi))
 | |
| 		return PTR_ERR(common->clk_smi);
 | |
| 
 | |
| 	pm_runtime_enable(dev);
 | |
| 	platform_set_drvdata(pdev, common);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static int mtk_smi_common_remove(struct platform_device *pdev)
 | |
| {
 | |
| 	pm_runtime_disable(&pdev->dev);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct of_device_id mtk_smi_common_of_ids[] = {
 | |
| 	{ .compatible = "mediatek,mt8173-smi-common", },
 | |
| 	{}
 | |
| };
 | |
| 
 | |
| static struct platform_driver mtk_smi_common_driver = {
 | |
| 	.probe	= mtk_smi_common_probe,
 | |
| 	.remove = mtk_smi_common_remove,
 | |
| 	.driver	= {
 | |
| 		.name = "mtk-smi-common",
 | |
| 		.of_match_table = mtk_smi_common_of_ids,
 | |
| 	}
 | |
| };
 | |
| 
 | |
| static int __init mtk_smi_init(void)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = platform_driver_register(&mtk_smi_common_driver);
 | |
| 	if (ret != 0) {
 | |
| 		pr_err("Failed to register SMI driver\n");
 | |
| 		return ret;
 | |
| 	}
 | |
| 
 | |
| 	ret = platform_driver_register(&mtk_smi_larb_driver);
 | |
| 	if (ret != 0) {
 | |
| 		pr_err("Failed to register SMI-LARB driver\n");
 | |
| 		goto err_unreg_smi;
 | |
| 	}
 | |
| 	return ret;
 | |
| 
 | |
| err_unreg_smi:
 | |
| 	platform_driver_unregister(&mtk_smi_common_driver);
 | |
| 	return ret;
 | |
| }
 | |
| subsys_initcall(mtk_smi_init);
 |