Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2018, Richtek Technology Corporation * * Richtek RT1711H Type-C Chip Driver */ #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/gpio/consumer.h> #include <linux/usb/tcpm.h> #include <linux/regmap.h> #include "tcpci.h" #define RT1711H_VID 0x29CF #define RT1711H_PID 0x1711 #define RT1711H_RTCTRL8 0x9B /* Autoidle timeout = (tout * 2 + 1) * 6.4ms */ #define RT1711H_RTCTRL8_SET(ck300, ship_off, auto_idle, tout) \ (((ck300) << 7) | ((ship_off) << 5) | \ ((auto_idle) << 3) | ((tout) & 0x07)) #define RT1711H_RTCTRL11 0x9E /* I2C timeout = (tout + 1) * 12.5ms */ #define RT1711H_RTCTRL11_SET(en, tout) \ (((en) << 7) | ((tout) & 0x0F)) #define RT1711H_RTCTRL13 0xA0 #define RT1711H_RTCTRL14 0xA1 #define RT1711H_RTCTRL15 0xA2 #define RT1711H_RTCTRL16 0xA3 struct rt1711h_chip { struct tcpci_data data; struct tcpci *tcpci; struct device *dev; }; static int rt1711h_read16(struct rt1711h_chip *chip, unsigned int reg, u16 *val) { return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u16)); } static int rt1711h_write16(struct rt1711h_chip *chip, unsigned int reg, u16 val) { return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u16)); } static int rt1711h_read8(struct rt1711h_chip *chip, unsigned int reg, u8 *val) { return regmap_raw_read(chip->data.regmap, reg, val, sizeof(u8)); } static int rt1711h_write8(struct rt1711h_chip *chip, unsigned int reg, u8 val) { return regmap_raw_write(chip->data.regmap, reg, &val, sizeof(u8)); } static const struct regmap_config rt1711h_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = 0xFF, /* 0x80 .. 0xFF are vendor defined */ }; static struct rt1711h_chip *tdata_to_rt1711h(struct tcpci_data *tdata) { return container_of(tdata, struct rt1711h_chip, data); } static int rt1711h_init(struct tcpci *tcpci, struct tcpci_data *tdata) { int ret; struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); /* CK 300K from 320K, shipping off, auto_idle enable, tout = 32ms */ ret = rt1711h_write8(chip, RT1711H_RTCTRL8, RT1711H_RTCTRL8_SET(0, 1, 1, 2)); if (ret < 0) return ret; /* I2C reset : (val + 1) * 12.5ms */ ret = rt1711h_write8(chip, RT1711H_RTCTRL11, RT1711H_RTCTRL11_SET(1, 0x0F)); if (ret < 0) return ret; /* tTCPCfilter : (26.7 * val) us */ ret = rt1711h_write8(chip, RT1711H_RTCTRL14, 0x0F); if (ret < 0) return ret; /* tDRP : (51.2 + 6.4 * val) ms */ ret = rt1711h_write8(chip, RT1711H_RTCTRL15, 0x04); if (ret < 0) return ret; /* dcSRC.DRP : 33% */ return rt1711h_write16(chip, RT1711H_RTCTRL16, 330); } static int rt1711h_set_vconn(struct tcpci *tcpci, struct tcpci_data *tdata, bool enable) { struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); return rt1711h_write8(chip, RT1711H_RTCTRL8, RT1711H_RTCTRL8_SET(0, 1, !enable, 2)); } static int rt1711h_start_drp_toggling(struct tcpci *tcpci, struct tcpci_data *tdata, enum typec_cc_status cc) { struct rt1711h_chip *chip = tdata_to_rt1711h(tdata); int ret; unsigned int reg = 0; switch (cc) { default: case TYPEC_CC_RP_DEF: reg |= (TCPC_ROLE_CTRL_RP_VAL_DEF << TCPC_ROLE_CTRL_RP_VAL_SHIFT); break; case TYPEC_CC_RP_1_5: reg |= (TCPC_ROLE_CTRL_RP_VAL_1_5 << TCPC_ROLE_CTRL_RP_VAL_SHIFT); break; case TYPEC_CC_RP_3_0: reg |= (TCPC_ROLE_CTRL_RP_VAL_3_0 << TCPC_ROLE_CTRL_RP_VAL_SHIFT); break; } if (cc == TYPEC_CC_RD) reg |= (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC1_SHIFT) | (TCPC_ROLE_CTRL_CC_RD << TCPC_ROLE_CTRL_CC2_SHIFT); else reg |= (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC1_SHIFT) | (TCPC_ROLE_CTRL_CC_RP << TCPC_ROLE_CTRL_CC2_SHIFT); ret = rt1711h_write8(chip, TCPC_ROLE_CTRL, reg); if (ret < 0) return ret; usleep_range(500, 1000); return 0; } static irqreturn_t rt1711h_irq(int irq, void *dev_id) { int ret; u16 alert; u8 status; struct rt1711h_chip *chip = dev_id; if (!chip->tcpci) return IRQ_HANDLED; ret = rt1711h_read16(chip, TCPC_ALERT, &alert); if (ret < 0) goto out; if (alert & TCPC_ALERT_CC_STATUS) { ret = rt1711h_read8(chip, TCPC_CC_STATUS, &status); if (ret < 0) goto out; /* Clear cc change event triggered by starting toggling */ if (status & TCPC_CC_STATUS_TOGGLING) rt1711h_write8(chip, TCPC_ALERT, TCPC_ALERT_CC_STATUS); } out: return tcpci_irq(chip->tcpci); } static int rt1711h_init_alert(struct rt1711h_chip *chip, struct i2c_client *client) { int ret; /* Disable chip interrupts before requesting irq */ ret = rt1711h_write16(chip, TCPC_ALERT_MASK, 0); if (ret < 0) return ret; ret = devm_request_threaded_irq(chip->dev, client->irq, NULL, rt1711h_irq, IRQF_ONESHOT | IRQF_TRIGGER_LOW, dev_name(chip->dev), chip); if (ret < 0) return ret; enable_irq_wake(client->irq); return 0; } static int rt1711h_sw_reset(struct rt1711h_chip *chip) { int ret; ret = rt1711h_write8(chip, RT1711H_RTCTRL13, 0x01); if (ret < 0) return ret; usleep_range(1000, 2000); return 0; } static int rt1711h_check_revision(struct i2c_client *i2c) { int ret; ret = i2c_smbus_read_word_data(i2c, TCPC_VENDOR_ID); if (ret < 0) return ret; if (ret != RT1711H_VID) { dev_err(&i2c->dev, "vid is not correct, 0x%04x\n", ret); return -ENODEV; } ret = i2c_smbus_read_word_data(i2c, TCPC_PRODUCT_ID); if (ret < 0) return ret; if (ret != RT1711H_PID) { dev_err(&i2c->dev, "pid is not correct, 0x%04x\n", ret); return -ENODEV; } return 0; } static int rt1711h_probe(struct i2c_client *client, const struct i2c_device_id *i2c_id) { int ret; struct rt1711h_chip *chip; ret = rt1711h_check_revision(client); if (ret < 0) { dev_err(&client->dev, "check vid/pid fail\n"); return ret; } chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->data.regmap = devm_regmap_init_i2c(client, &rt1711h_regmap_config); if (IS_ERR(chip->data.regmap)) return PTR_ERR(chip->data.regmap); chip->dev = &client->dev; i2c_set_clientdata(client, chip); ret = rt1711h_sw_reset(chip); if (ret < 0) return ret; ret = rt1711h_init_alert(chip, client); if (ret < 0) return ret; chip->data.init = rt1711h_init; chip->data.set_vconn = rt1711h_set_vconn; chip->data.start_drp_toggling = rt1711h_start_drp_toggling; chip->tcpci = tcpci_register_port(chip->dev, &chip->data); if (IS_ERR_OR_NULL(chip->tcpci)) return PTR_ERR(chip->tcpci); return 0; } static int rt1711h_remove(struct i2c_client *client) { struct rt1711h_chip *chip = i2c_get_clientdata(client); tcpci_unregister_port(chip->tcpci); return 0; } static const struct i2c_device_id rt1711h_id[] = { { "rt1711h", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, rt1711h_id); #ifdef CONFIG_OF static const struct of_device_id rt1711h_of_match[] = { { .compatible = "richtek,rt1711h", }, {}, }; MODULE_DEVICE_TABLE(of, rt1711h_of_match); #endif static struct i2c_driver rt1711h_i2c_driver = { .driver = { .name = "rt1711h", .of_match_table = of_match_ptr(rt1711h_of_match), }, .probe = rt1711h_probe, .remove = rt1711h_remove, .id_table = rt1711h_id, }; module_i2c_driver(rt1711h_i2c_driver); MODULE_AUTHOR("ShuFan Lee <shufan_lee@richtek.com>"); MODULE_DESCRIPTION("RT1711H USB Type-C Port Controller Interface Driver"); MODULE_LICENSE("GPL"); |