1e1aefcddSChiYuan Huang // SPDX-License-Identifier: GPL-2.0-only
2e1aefcddSChiYuan Huang /*
3e1aefcddSChiYuan Huang  * Copyright (C) 2020 MediaTek Inc.
4e1aefcddSChiYuan Huang  *
5e1aefcddSChiYuan Huang  * Author: ChiYuan Huang <cy_huang@richtek.com>
6e1aefcddSChiYuan Huang  */
7e1aefcddSChiYuan Huang 
8e1aefcddSChiYuan Huang #include <linux/interrupt.h>
9e1aefcddSChiYuan Huang #include <linux/kernel.h>
10e1aefcddSChiYuan Huang #include <linux/module.h>
11e1aefcddSChiYuan Huang #include <linux/of.h>
12e1aefcddSChiYuan Huang #include <linux/platform_device.h>
13e1aefcddSChiYuan Huang #include <linux/regmap.h>
147963d4d7SXin Ji #include <linux/usb/tcpci.h>
15e1aefcddSChiYuan Huang #include <linux/usb/tcpm.h>
16e1aefcddSChiYuan Huang 
174031cd95SChiYuan Huang #define MT6360_REG_PHYCTRL1	0x80
184031cd95SChiYuan Huang #define MT6360_REG_PHYCTRL3	0x82
194031cd95SChiYuan Huang #define MT6360_REG_PHYCTRL7	0x86
20e1aefcddSChiYuan Huang #define MT6360_REG_VCONNCTRL1	0x8C
21e1aefcddSChiYuan Huang #define MT6360_REG_MODECTRL2	0x8F
22e1aefcddSChiYuan Huang #define MT6360_REG_SWRESET	0xA0
23e1aefcddSChiYuan Huang #define MT6360_REG_DEBCTRL1	0xA1
24e1aefcddSChiYuan Huang #define MT6360_REG_DRPCTRL1	0xA2
25e1aefcddSChiYuan Huang #define MT6360_REG_DRPCTRL2	0xA3
26e1aefcddSChiYuan Huang #define MT6360_REG_I2CTORST	0xBF
274031cd95SChiYuan Huang #define MT6360_REG_PHYCTRL11	0xCA
284031cd95SChiYuan Huang #define MT6360_REG_RXCTRL1	0xCE
29e1aefcddSChiYuan Huang #define MT6360_REG_RXCTRL2	0xCF
30e1aefcddSChiYuan Huang #define MT6360_REG_CTDCTRL2	0xEC
31e1aefcddSChiYuan Huang 
32e1aefcddSChiYuan Huang /* MT6360_REG_VCONNCTRL1 */
33e1aefcddSChiYuan Huang #define MT6360_VCONNCL_ENABLE	BIT(0)
34e1aefcddSChiYuan Huang /* MT6360_REG_RXCTRL2 */
35e1aefcddSChiYuan Huang #define MT6360_OPEN40M_ENABLE	BIT(7)
36e1aefcddSChiYuan Huang /* MT6360_REG_CTDCTRL2 */
37e1aefcddSChiYuan Huang #define MT6360_RPONESHOT_ENABLE	BIT(6)
38e1aefcddSChiYuan Huang 
39e1aefcddSChiYuan Huang struct mt6360_tcpc_info {
40e1aefcddSChiYuan Huang 	struct tcpci_data tdata;
41e1aefcddSChiYuan Huang 	struct tcpci *tcpci;
42e1aefcddSChiYuan Huang 	struct device *dev;
43e1aefcddSChiYuan Huang 	int irq;
44e1aefcddSChiYuan Huang };
45e1aefcddSChiYuan Huang 
mt6360_tcpc_write16(struct regmap * regmap,unsigned int reg,u16 val)46e1aefcddSChiYuan Huang static inline int mt6360_tcpc_write16(struct regmap *regmap,
47e1aefcddSChiYuan Huang 				      unsigned int reg, u16 val)
48e1aefcddSChiYuan Huang {
49e1aefcddSChiYuan Huang 	return regmap_raw_write(regmap, reg, &val, sizeof(u16));
50e1aefcddSChiYuan Huang }
51e1aefcddSChiYuan Huang 
mt6360_tcpc_init(struct tcpci * tcpci,struct tcpci_data * tdata)52e1aefcddSChiYuan Huang static int mt6360_tcpc_init(struct tcpci *tcpci, struct tcpci_data *tdata)
53e1aefcddSChiYuan Huang {
54e1aefcddSChiYuan Huang 	struct regmap *regmap = tdata->regmap;
55e1aefcddSChiYuan Huang 	int ret;
56e1aefcddSChiYuan Huang 
57e1aefcddSChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_SWRESET, 0x01);
58e1aefcddSChiYuan Huang 	if (ret)
59e1aefcddSChiYuan Huang 		return ret;
60e1aefcddSChiYuan Huang 
61e1aefcddSChiYuan Huang 	/* after reset command, wait 1~2ms to wait IC action */
62e1aefcddSChiYuan Huang 	usleep_range(1000, 2000);
63e1aefcddSChiYuan Huang 
64e1aefcddSChiYuan Huang 	/* write all alert to masked */
65e1aefcddSChiYuan Huang 	ret = mt6360_tcpc_write16(regmap, TCPC_ALERT_MASK, 0);
66e1aefcddSChiYuan Huang 	if (ret)
67e1aefcddSChiYuan Huang 		return ret;
68e1aefcddSChiYuan Huang 
69e1aefcddSChiYuan Huang 	/* config I2C timeout reset enable , and timeout to 200ms */
70e1aefcddSChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_I2CTORST, 0x8F);
71e1aefcddSChiYuan Huang 	if (ret)
72e1aefcddSChiYuan Huang 		return ret;
73e1aefcddSChiYuan Huang 
74e1aefcddSChiYuan Huang 	/* config CC Detect Debounce : 26.7*val us */
75e1aefcddSChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_DEBCTRL1, 0x10);
76e1aefcddSChiYuan Huang 	if (ret)
77e1aefcddSChiYuan Huang 		return ret;
78e1aefcddSChiYuan Huang 
79e1aefcddSChiYuan Huang 	/* DRP Toggle Cycle : 51.2 + 6.4*val ms */
80e1aefcddSChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_DRPCTRL1, 4);
81e1aefcddSChiYuan Huang 	if (ret)
82e1aefcddSChiYuan Huang 		return ret;
83e1aefcddSChiYuan Huang 
84e1aefcddSChiYuan Huang 	/* DRP Duyt Ctrl : dcSRC: /1024 */
85e1aefcddSChiYuan Huang 	ret = mt6360_tcpc_write16(regmap, MT6360_REG_DRPCTRL2, 330);
86e1aefcddSChiYuan Huang 	if (ret)
87e1aefcddSChiYuan Huang 		return ret;
88e1aefcddSChiYuan Huang 
89e1aefcddSChiYuan Huang 	/* Enable VCONN Current Limit function */
90e1aefcddSChiYuan Huang 	ret = regmap_update_bits(regmap, MT6360_REG_VCONNCTRL1, MT6360_VCONNCL_ENABLE,
91e1aefcddSChiYuan Huang 				 MT6360_VCONNCL_ENABLE);
92e1aefcddSChiYuan Huang 	if (ret)
93e1aefcddSChiYuan Huang 		return ret;
94e1aefcddSChiYuan Huang 
95e1aefcddSChiYuan Huang 	/* Enable cc open 40ms when pmic send vsysuv signal */
96e1aefcddSChiYuan Huang 	ret = regmap_update_bits(regmap, MT6360_REG_RXCTRL2, MT6360_OPEN40M_ENABLE,
97e1aefcddSChiYuan Huang 				 MT6360_OPEN40M_ENABLE);
98e1aefcddSChiYuan Huang 	if (ret)
99e1aefcddSChiYuan Huang 		return ret;
100e1aefcddSChiYuan Huang 
101e1aefcddSChiYuan Huang 	/* Enable Rpdet oneshot detection */
102e1aefcddSChiYuan Huang 	ret = regmap_update_bits(regmap, MT6360_REG_CTDCTRL2, MT6360_RPONESHOT_ENABLE,
103e1aefcddSChiYuan Huang 				 MT6360_RPONESHOT_ENABLE);
104e1aefcddSChiYuan Huang 	if (ret)
105e1aefcddSChiYuan Huang 		return ret;
106e1aefcddSChiYuan Huang 
1074031cd95SChiYuan Huang 	/* BMC PHY */
1084031cd95SChiYuan Huang 	ret = mt6360_tcpc_write16(regmap, MT6360_REG_PHYCTRL1, 0x3A70);
1094031cd95SChiYuan Huang 	if (ret)
1104031cd95SChiYuan Huang 		return ret;
1114031cd95SChiYuan Huang 
1124031cd95SChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_PHYCTRL3,  0x82);
1134031cd95SChiYuan Huang 	if (ret)
1144031cd95SChiYuan Huang 		return ret;
1154031cd95SChiYuan Huang 
1164031cd95SChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_PHYCTRL7, 0x36);
1174031cd95SChiYuan Huang 	if (ret)
1184031cd95SChiYuan Huang 		return ret;
1194031cd95SChiYuan Huang 
1204031cd95SChiYuan Huang 	ret = mt6360_tcpc_write16(regmap, MT6360_REG_PHYCTRL11, 0x3C60);
1214031cd95SChiYuan Huang 	if (ret)
1224031cd95SChiYuan Huang 		return ret;
1234031cd95SChiYuan Huang 
1244031cd95SChiYuan Huang 	ret = regmap_write(regmap, MT6360_REG_RXCTRL1, 0xE8);
1254031cd95SChiYuan Huang 	if (ret)
1264031cd95SChiYuan Huang 		return ret;
1274031cd95SChiYuan Huang 
128e1aefcddSChiYuan Huang 	/* Set shipping mode off, AUTOIDLE on */
129e1aefcddSChiYuan Huang 	return regmap_write(regmap, MT6360_REG_MODECTRL2, 0x7A);
130e1aefcddSChiYuan Huang }
131e1aefcddSChiYuan Huang 
mt6360_irq(int irq,void * dev_id)132e1aefcddSChiYuan Huang static irqreturn_t mt6360_irq(int irq, void *dev_id)
133e1aefcddSChiYuan Huang {
134e1aefcddSChiYuan Huang 	struct mt6360_tcpc_info *mti = dev_id;
135e1aefcddSChiYuan Huang 
136e1aefcddSChiYuan Huang 	return tcpci_irq(mti->tcpci);
137e1aefcddSChiYuan Huang }
138e1aefcddSChiYuan Huang 
mt6360_tcpc_probe(struct platform_device * pdev)139e1aefcddSChiYuan Huang static int mt6360_tcpc_probe(struct platform_device *pdev)
140e1aefcddSChiYuan Huang {
141e1aefcddSChiYuan Huang 	struct mt6360_tcpc_info *mti;
142e1aefcddSChiYuan Huang 	int ret;
143e1aefcddSChiYuan Huang 
144e1aefcddSChiYuan Huang 	mti = devm_kzalloc(&pdev->dev, sizeof(*mti), GFP_KERNEL);
145e1aefcddSChiYuan Huang 	if (!mti)
146e1aefcddSChiYuan Huang 		return -ENOMEM;
147e1aefcddSChiYuan Huang 
148e1aefcddSChiYuan Huang 	mti->dev = &pdev->dev;
149e1aefcddSChiYuan Huang 
150e1aefcddSChiYuan Huang 	mti->tdata.regmap = dev_get_regmap(pdev->dev.parent, NULL);
151e1aefcddSChiYuan Huang 	if (!mti->tdata.regmap) {
152e1aefcddSChiYuan Huang 		dev_err(&pdev->dev, "Failed to get parent regmap\n");
153e1aefcddSChiYuan Huang 		return -ENODEV;
154e1aefcddSChiYuan Huang 	}
155e1aefcddSChiYuan Huang 
156e1aefcddSChiYuan Huang 	mti->irq = platform_get_irq_byname(pdev, "PD_IRQB");
157e1aefcddSChiYuan Huang 	if (mti->irq < 0)
158e1aefcddSChiYuan Huang 		return mti->irq;
159e1aefcddSChiYuan Huang 
160e1aefcddSChiYuan Huang 	mti->tdata.init = mt6360_tcpc_init;
161e1aefcddSChiYuan Huang 	mti->tcpci = tcpci_register_port(&pdev->dev, &mti->tdata);
162e1aefcddSChiYuan Huang 	if (IS_ERR(mti->tcpci)) {
163e1aefcddSChiYuan Huang 		dev_err(&pdev->dev, "Failed to register tcpci port\n");
164e1aefcddSChiYuan Huang 		return PTR_ERR(mti->tcpci);
165e1aefcddSChiYuan Huang 	}
166e1aefcddSChiYuan Huang 
167e1aefcddSChiYuan Huang 	ret = devm_request_threaded_irq(mti->dev, mti->irq, NULL, mt6360_irq, IRQF_ONESHOT,
168e1aefcddSChiYuan Huang 					dev_name(&pdev->dev), mti);
169e1aefcddSChiYuan Huang 	if (ret) {
170e1aefcddSChiYuan Huang 		dev_err(mti->dev, "Failed to register irq\n");
171e1aefcddSChiYuan Huang 		tcpci_unregister_port(mti->tcpci);
172e1aefcddSChiYuan Huang 		return ret;
173e1aefcddSChiYuan Huang 	}
174e1aefcddSChiYuan Huang 
175e1aefcddSChiYuan Huang 	device_init_wakeup(&pdev->dev, true);
176e1aefcddSChiYuan Huang 	platform_set_drvdata(pdev, mti);
177e1aefcddSChiYuan Huang 
178e1aefcddSChiYuan Huang 	return 0;
179e1aefcddSChiYuan Huang }
180e1aefcddSChiYuan Huang 
mt6360_tcpc_remove(struct platform_device * pdev)181*42c78cfaSUwe Kleine-König static void mt6360_tcpc_remove(struct platform_device *pdev)
182e1aefcddSChiYuan Huang {
183e1aefcddSChiYuan Huang 	struct mt6360_tcpc_info *mti = platform_get_drvdata(pdev);
184e1aefcddSChiYuan Huang 
185e1aefcddSChiYuan Huang 	disable_irq(mti->irq);
186e1aefcddSChiYuan Huang 	tcpci_unregister_port(mti->tcpci);
187e1aefcddSChiYuan Huang }
188e1aefcddSChiYuan Huang 
mt6360_tcpc_suspend(struct device * dev)189e1aefcddSChiYuan Huang static int __maybe_unused mt6360_tcpc_suspend(struct device *dev)
190e1aefcddSChiYuan Huang {
191e1aefcddSChiYuan Huang 	struct mt6360_tcpc_info *mti = dev_get_drvdata(dev);
192e1aefcddSChiYuan Huang 
193e1aefcddSChiYuan Huang 	if (device_may_wakeup(dev))
194e1aefcddSChiYuan Huang 		enable_irq_wake(mti->irq);
195e1aefcddSChiYuan Huang 
196e1aefcddSChiYuan Huang 	return 0;
197e1aefcddSChiYuan Huang }
198e1aefcddSChiYuan Huang 
mt6360_tcpc_resume(struct device * dev)199e1aefcddSChiYuan Huang static int __maybe_unused mt6360_tcpc_resume(struct device *dev)
200e1aefcddSChiYuan Huang {
201e1aefcddSChiYuan Huang 	struct mt6360_tcpc_info *mti = dev_get_drvdata(dev);
202e1aefcddSChiYuan Huang 
203e1aefcddSChiYuan Huang 	if (device_may_wakeup(dev))
204e1aefcddSChiYuan Huang 		disable_irq_wake(mti->irq);
205e1aefcddSChiYuan Huang 
206e1aefcddSChiYuan Huang 	return 0;
207e1aefcddSChiYuan Huang }
208e1aefcddSChiYuan Huang 
209e1aefcddSChiYuan Huang static SIMPLE_DEV_PM_OPS(mt6360_tcpc_pm_ops, mt6360_tcpc_suspend, mt6360_tcpc_resume);
210e1aefcddSChiYuan Huang 
211e1aefcddSChiYuan Huang static const struct of_device_id __maybe_unused mt6360_tcpc_of_id[] = {
212e1aefcddSChiYuan Huang 	{ .compatible = "mediatek,mt6360-tcpc", },
213e1aefcddSChiYuan Huang 	{},
214e1aefcddSChiYuan Huang };
215e1aefcddSChiYuan Huang MODULE_DEVICE_TABLE(of, mt6360_tcpc_of_id);
216e1aefcddSChiYuan Huang 
217e1aefcddSChiYuan Huang static struct platform_driver mt6360_tcpc_driver = {
218e1aefcddSChiYuan Huang 	.driver = {
219e1aefcddSChiYuan Huang 		.name = "mt6360-tcpc",
220e1aefcddSChiYuan Huang 		.pm = &mt6360_tcpc_pm_ops,
221e1aefcddSChiYuan Huang 		.of_match_table = mt6360_tcpc_of_id,
222e1aefcddSChiYuan Huang 	},
223e1aefcddSChiYuan Huang 	.probe = mt6360_tcpc_probe,
224*42c78cfaSUwe Kleine-König 	.remove_new = mt6360_tcpc_remove,
225e1aefcddSChiYuan Huang };
226e1aefcddSChiYuan Huang module_platform_driver(mt6360_tcpc_driver);
227e1aefcddSChiYuan Huang 
228e1aefcddSChiYuan Huang MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
229e1aefcddSChiYuan Huang MODULE_DESCRIPTION("MT6360 USB Type-C Port Controller Interface Driver");
230e1aefcddSChiYuan Huang MODULE_LICENSE("GPL v2");
231