xref: /linux/drivers/i2c/busses/i2c-designware-amdisp.c (revision 2f5606afa4c2bcabd45cb34c92faf93ca5ffe75e)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Based on Synopsys DesignWare I2C adapter driver.
4  *
5  * Copyright (C) 2025 Advanced Micro Devices, Inc.
6  */
7 
8 #include <linux/module.h>
9 #include <linux/platform_device.h>
10 #include <linux/pm_runtime.h>
11 
12 #include "i2c-designware-core.h"
13 
14 #define DRV_NAME		"amd_isp_i2c_designware"
15 #define AMD_ISP_I2C_INPUT_CLK	100 /* Mhz */
16 
17 static void amd_isp_dw_i2c_plat_pm_cleanup(struct dw_i2c_dev *i2c_dev)
18 {
19 	pm_runtime_disable(i2c_dev->dev);
20 
21 	if (i2c_dev->shared_with_punit)
22 		pm_runtime_put_noidle(i2c_dev->dev);
23 }
24 
25 static inline u32 amd_isp_dw_i2c_get_clk_rate(struct dw_i2c_dev *i2c_dev)
26 {
27 	return AMD_ISP_I2C_INPUT_CLK * 1000;
28 }
29 
30 static int amd_isp_dw_i2c_plat_probe(struct platform_device *pdev)
31 {
32 	struct dw_i2c_dev *isp_i2c_dev;
33 	struct i2c_adapter *adap;
34 	int ret;
35 
36 	isp_i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*isp_i2c_dev), GFP_KERNEL);
37 	if (!isp_i2c_dev)
38 		return -ENOMEM;
39 	isp_i2c_dev->dev = &pdev->dev;
40 
41 	pdev->dev.init_name = DRV_NAME;
42 
43 	/*
44 	 * Use the polling mode to send/receive the data, because
45 	 * no IRQ connection from ISP I2C
46 	 */
47 	isp_i2c_dev->flags |= ACCESS_POLLING;
48 	platform_set_drvdata(pdev, isp_i2c_dev);
49 
50 	isp_i2c_dev->base = devm_platform_ioremap_resource(pdev, 0);
51 	if (IS_ERR(isp_i2c_dev->base))
52 		return dev_err_probe(&pdev->dev, PTR_ERR(isp_i2c_dev->base),
53 				     "failed to get IOMEM resource\n");
54 
55 	isp_i2c_dev->get_clk_rate_khz = amd_isp_dw_i2c_get_clk_rate;
56 	ret = i2c_dw_fw_parse_and_configure(isp_i2c_dev);
57 	if (ret)
58 		return dev_err_probe(&pdev->dev, ret,
59 				     "failed to parse i2c dw fwnode and configure\n");
60 
61 	i2c_dw_configure(isp_i2c_dev);
62 
63 	adap = &isp_i2c_dev->adapter;
64 	adap->owner = THIS_MODULE;
65 	ACPI_COMPANION_SET(&adap->dev, ACPI_COMPANION(&pdev->dev));
66 	adap->dev.of_node = pdev->dev.of_node;
67 	/* use dynamically allocated adapter id */
68 	adap->nr = -1;
69 
70 	if (isp_i2c_dev->flags & ACCESS_NO_IRQ_SUSPEND)
71 		dev_pm_set_driver_flags(&pdev->dev,
72 					DPM_FLAG_SMART_PREPARE);
73 	else
74 		dev_pm_set_driver_flags(&pdev->dev,
75 					DPM_FLAG_SMART_PREPARE |
76 					DPM_FLAG_SMART_SUSPEND);
77 
78 	device_enable_async_suspend(&pdev->dev);
79 
80 	if (isp_i2c_dev->shared_with_punit)
81 		pm_runtime_get_noresume(&pdev->dev);
82 
83 	pm_runtime_enable(&pdev->dev);
84 	pm_runtime_get_sync(&pdev->dev);
85 
86 	ret = i2c_dw_probe(isp_i2c_dev);
87 	if (ret) {
88 		dev_err_probe(&pdev->dev, ret, "i2c_dw_probe failed\n");
89 		goto error_release_rpm;
90 	}
91 
92 	pm_runtime_put_sync(&pdev->dev);
93 
94 	return 0;
95 
96 error_release_rpm:
97 	amd_isp_dw_i2c_plat_pm_cleanup(isp_i2c_dev);
98 	pm_runtime_put_sync(&pdev->dev);
99 	return ret;
100 }
101 
102 static void amd_isp_dw_i2c_plat_remove(struct platform_device *pdev)
103 {
104 	struct dw_i2c_dev *isp_i2c_dev = platform_get_drvdata(pdev);
105 
106 	pm_runtime_get_sync(&pdev->dev);
107 
108 	i2c_del_adapter(&isp_i2c_dev->adapter);
109 
110 	i2c_dw_disable(isp_i2c_dev);
111 
112 	pm_runtime_put_sync(&pdev->dev);
113 	amd_isp_dw_i2c_plat_pm_cleanup(isp_i2c_dev);
114 }
115 
116 static int amd_isp_dw_i2c_plat_prepare(struct device *dev)
117 {
118 	/*
119 	 * If the ACPI companion device object is present for this device, it
120 	 * may be accessed during suspend and resume of other devices via I2C
121 	 * operation regions, so tell the PM core and middle layers to avoid
122 	 * skipping system suspend/resume callbacks for it in that case.
123 	 */
124 	return !has_acpi_companion(dev);
125 }
126 
127 static int amd_isp_dw_i2c_plat_runtime_suspend(struct device *dev)
128 {
129 	struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
130 
131 	if (i_dev->shared_with_punit)
132 		return 0;
133 
134 	i2c_dw_disable(i_dev);
135 	i2c_dw_prepare_clk(i_dev, false);
136 
137 	return 0;
138 }
139 
140 static int amd_isp_dw_i2c_plat_suspend(struct device *dev)
141 {
142 	struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
143 	int ret;
144 
145 	if (!i_dev)
146 		return -ENODEV;
147 
148 	ret = amd_isp_dw_i2c_plat_runtime_suspend(dev);
149 	if (!ret)
150 		i2c_mark_adapter_suspended(&i_dev->adapter);
151 
152 	return ret;
153 }
154 
155 static int amd_isp_dw_i2c_plat_runtime_resume(struct device *dev)
156 {
157 	struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
158 
159 	if (!i_dev)
160 		return -ENODEV;
161 
162 	if (!i_dev->shared_with_punit)
163 		i2c_dw_prepare_clk(i_dev, true);
164 	if (i_dev->init)
165 		i_dev->init(i_dev);
166 
167 	return 0;
168 }
169 
170 static int amd_isp_dw_i2c_plat_resume(struct device *dev)
171 {
172 	struct dw_i2c_dev *i_dev = dev_get_drvdata(dev);
173 
174 	amd_isp_dw_i2c_plat_runtime_resume(dev);
175 	i2c_mark_adapter_resumed(&i_dev->adapter);
176 
177 	return 0;
178 }
179 
180 static const struct dev_pm_ops amd_isp_dw_i2c_dev_pm_ops = {
181 	.prepare = pm_sleep_ptr(amd_isp_dw_i2c_plat_prepare),
182 	LATE_SYSTEM_SLEEP_PM_OPS(amd_isp_dw_i2c_plat_suspend, amd_isp_dw_i2c_plat_resume)
183 	RUNTIME_PM_OPS(amd_isp_dw_i2c_plat_runtime_suspend, amd_isp_dw_i2c_plat_runtime_resume, NULL)
184 };
185 
186 /* Work with hotplug and coldplug */
187 MODULE_ALIAS("platform:amd_isp_i2c_designware");
188 
189 static struct platform_driver amd_isp_dw_i2c_driver = {
190 	.probe = amd_isp_dw_i2c_plat_probe,
191 	.remove = amd_isp_dw_i2c_plat_remove,
192 	.driver		= {
193 		.name	= DRV_NAME,
194 		.pm	= pm_ptr(&amd_isp_dw_i2c_dev_pm_ops),
195 	},
196 };
197 module_platform_driver(amd_isp_dw_i2c_driver);
198 
199 MODULE_DESCRIPTION("Synopsys DesignWare I2C bus adapter in AMD ISP");
200 MODULE_IMPORT_NS("I2C_DW");
201 MODULE_IMPORT_NS("I2C_DW_COMMON");
202 MODULE_AUTHOR("Venkata Narendra Kumar Gutta <vengutta@amd.com>");
203 MODULE_AUTHOR("Pratap Nirujogi <pratap.nirujogi@amd.com>");
204 MODULE_AUTHOR("Bin Du <bin.du@amd.com>");
205 MODULE_LICENSE("GPL");
206