pi3usb30532.c (4315B)
1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Pericom PI3USB30532 Type-C cross switch / mux driver 4 * 5 * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com> 6 */ 7 8#include <linux/i2c.h> 9#include <linux/kernel.h> 10#include <linux/module.h> 11#include <linux/mutex.h> 12#include <linux/usb/typec_dp.h> 13#include <linux/usb/typec_mux.h> 14 15#define PI3USB30532_CONF 0x00 16 17#define PI3USB30532_CONF_OPEN 0x00 18#define PI3USB30532_CONF_SWAP 0x01 19#define PI3USB30532_CONF_4LANE_DP 0x02 20#define PI3USB30532_CONF_USB3 0x04 21#define PI3USB30532_CONF_USB3_AND_2LANE_DP 0x06 22 23struct pi3usb30532 { 24 struct i2c_client *client; 25 struct mutex lock; /* protects the cached conf register */ 26 struct typec_switch_dev *sw; 27 struct typec_mux_dev *mux; 28 u8 conf; 29}; 30 31static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf) 32{ 33 int ret = 0; 34 35 if (pi->conf == new_conf) 36 return 0; 37 38 ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf); 39 if (ret) { 40 dev_err(&pi->client->dev, "Error writing conf: %d\n", ret); 41 return ret; 42 } 43 44 pi->conf = new_conf; 45 return 0; 46} 47 48static int pi3usb30532_sw_set(struct typec_switch_dev *sw, 49 enum typec_orientation orientation) 50{ 51 struct pi3usb30532 *pi = typec_switch_get_drvdata(sw); 52 u8 new_conf; 53 int ret; 54 55 mutex_lock(&pi->lock); 56 new_conf = pi->conf; 57 58 switch (orientation) { 59 case TYPEC_ORIENTATION_NONE: 60 new_conf = PI3USB30532_CONF_OPEN; 61 break; 62 case TYPEC_ORIENTATION_NORMAL: 63 new_conf &= ~PI3USB30532_CONF_SWAP; 64 break; 65 case TYPEC_ORIENTATION_REVERSE: 66 new_conf |= PI3USB30532_CONF_SWAP; 67 break; 68 } 69 70 ret = pi3usb30532_set_conf(pi, new_conf); 71 mutex_unlock(&pi->lock); 72 73 return ret; 74} 75 76static int 77pi3usb30532_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state) 78{ 79 struct pi3usb30532 *pi = typec_mux_get_drvdata(mux); 80 u8 new_conf; 81 int ret; 82 83 mutex_lock(&pi->lock); 84 new_conf = pi->conf; 85 86 switch (state->mode) { 87 case TYPEC_STATE_SAFE: 88 new_conf = (new_conf & PI3USB30532_CONF_SWAP) | 89 PI3USB30532_CONF_OPEN; 90 break; 91 case TYPEC_STATE_USB: 92 new_conf = (new_conf & PI3USB30532_CONF_SWAP) | 93 PI3USB30532_CONF_USB3; 94 break; 95 case TYPEC_DP_STATE_C: 96 case TYPEC_DP_STATE_E: 97 new_conf = (new_conf & PI3USB30532_CONF_SWAP) | 98 PI3USB30532_CONF_4LANE_DP; 99 break; 100 case TYPEC_DP_STATE_D: 101 new_conf = (new_conf & PI3USB30532_CONF_SWAP) | 102 PI3USB30532_CONF_USB3_AND_2LANE_DP; 103 break; 104 default: 105 break; 106 } 107 108 ret = pi3usb30532_set_conf(pi, new_conf); 109 mutex_unlock(&pi->lock); 110 111 return ret; 112} 113 114static int pi3usb30532_probe(struct i2c_client *client) 115{ 116 struct device *dev = &client->dev; 117 struct typec_switch_desc sw_desc = { }; 118 struct typec_mux_desc mux_desc = { }; 119 struct pi3usb30532 *pi; 120 int ret; 121 122 pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL); 123 if (!pi) 124 return -ENOMEM; 125 126 pi->client = client; 127 mutex_init(&pi->lock); 128 129 ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF); 130 if (ret < 0) { 131 dev_err(dev, "Error reading config register %d\n", ret); 132 return ret; 133 } 134 pi->conf = ret; 135 136 sw_desc.drvdata = pi; 137 sw_desc.fwnode = dev->fwnode; 138 sw_desc.set = pi3usb30532_sw_set; 139 140 pi->sw = typec_switch_register(dev, &sw_desc); 141 if (IS_ERR(pi->sw)) { 142 dev_err(dev, "Error registering typec switch: %ld\n", 143 PTR_ERR(pi->sw)); 144 return PTR_ERR(pi->sw); 145 } 146 147 mux_desc.drvdata = pi; 148 mux_desc.fwnode = dev->fwnode; 149 mux_desc.set = pi3usb30532_mux_set; 150 151 pi->mux = typec_mux_register(dev, &mux_desc); 152 if (IS_ERR(pi->mux)) { 153 typec_switch_unregister(pi->sw); 154 dev_err(dev, "Error registering typec mux: %ld\n", 155 PTR_ERR(pi->mux)); 156 return PTR_ERR(pi->mux); 157 } 158 159 i2c_set_clientdata(client, pi); 160 return 0; 161} 162 163static int pi3usb30532_remove(struct i2c_client *client) 164{ 165 struct pi3usb30532 *pi = i2c_get_clientdata(client); 166 167 typec_mux_unregister(pi->mux); 168 typec_switch_unregister(pi->sw); 169 return 0; 170} 171 172static const struct i2c_device_id pi3usb30532_table[] = { 173 { "pi3usb30532" }, 174 { } 175}; 176MODULE_DEVICE_TABLE(i2c, pi3usb30532_table); 177 178static struct i2c_driver pi3usb30532_driver = { 179 .driver = { 180 .name = "pi3usb30532", 181 }, 182 .probe_new = pi3usb30532_probe, 183 .remove = pi3usb30532_remove, 184 .id_table = pi3usb30532_table, 185}; 186 187module_i2c_driver(pi3usb30532_driver); 188 189MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); 190MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver"); 191MODULE_LICENSE("GPL");