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