| .. | .. |
|---|
| 7 | 7 | * Author: Chunfeng Yun <chunfeng.yun@mediatek.com> |
|---|
| 8 | 8 | */ |
|---|
| 9 | 9 | |
|---|
| 10 | | -#include <linux/debugfs.h> |
|---|
| 11 | | -#include <linux/irq.h> |
|---|
| 12 | | -#include <linux/kernel.h> |
|---|
| 13 | | -#include <linux/of_device.h> |
|---|
| 14 | | -#include <linux/pinctrl/consumer.h> |
|---|
| 15 | | -#include <linux/seq_file.h> |
|---|
| 16 | | -#include <linux/uaccess.h> |
|---|
| 10 | +#include <linux/usb/role.h> |
|---|
| 17 | 11 | |
|---|
| 18 | 12 | #include "mtu3.h" |
|---|
| 19 | 13 | #include "mtu3_dr.h" |
|---|
| 14 | +#include "mtu3_debug.h" |
|---|
| 20 | 15 | |
|---|
| 21 | 16 | #define USB2_PORT 2 |
|---|
| 22 | 17 | #define USB3_PORT 3 |
|---|
| .. | .. |
|---|
| 28 | 23 | MTU3_VBUS_VALID, |
|---|
| 29 | 24 | }; |
|---|
| 30 | 25 | |
|---|
| 26 | +static char *mailbox_state_string(enum mtu3_vbus_id_state state) |
|---|
| 27 | +{ |
|---|
| 28 | + switch (state) { |
|---|
| 29 | + case MTU3_ID_FLOAT: |
|---|
| 30 | + return "ID_FLOAT"; |
|---|
| 31 | + case MTU3_ID_GROUND: |
|---|
| 32 | + return "ID_GROUND"; |
|---|
| 33 | + case MTU3_VBUS_OFF: |
|---|
| 34 | + return "VBUS_OFF"; |
|---|
| 35 | + case MTU3_VBUS_VALID: |
|---|
| 36 | + return "VBUS_VALID"; |
|---|
| 37 | + default: |
|---|
| 38 | + return "UNKNOWN"; |
|---|
| 39 | + } |
|---|
| 40 | +} |
|---|
| 41 | + |
|---|
| 31 | 42 | static void toggle_opstate(struct ssusb_mtk *ssusb) |
|---|
| 32 | 43 | { |
|---|
| 33 | | - if (!ssusb->otg_switch.is_u3_drd) { |
|---|
| 34 | | - mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); |
|---|
| 35 | | - mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); |
|---|
| 36 | | - } |
|---|
| 44 | + mtu3_setbits(ssusb->mac_base, U3D_DEVICE_CONTROL, DC_SESSION); |
|---|
| 45 | + mtu3_setbits(ssusb->mac_base, U3D_POWER_MANAGEMENT, SOFT_CONN); |
|---|
| 37 | 46 | } |
|---|
| 38 | 47 | |
|---|
| 39 | 48 | /* only port0 supports dual-role mode */ |
|---|
| .. | .. |
|---|
| 147 | 156 | container_of(otg_sx, struct ssusb_mtk, otg_switch); |
|---|
| 148 | 157 | struct mtu3 *mtu = ssusb->u3d; |
|---|
| 149 | 158 | |
|---|
| 150 | | - dev_dbg(ssusb->dev, "mailbox state(%d)\n", status); |
|---|
| 159 | + dev_dbg(ssusb->dev, "mailbox %s\n", mailbox_state_string(status)); |
|---|
| 160 | + mtu3_dbg_trace(ssusb->dev, "mailbox %s", mailbox_state_string(status)); |
|---|
| 151 | 161 | |
|---|
| 152 | 162 | switch (status) { |
|---|
| 153 | 163 | case MTU3_ID_GROUND: |
|---|
| .. | .. |
|---|
| 238 | 248 | otg_sx->vbus_nb.notifier_call = ssusb_vbus_notifier; |
|---|
| 239 | 249 | ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB, |
|---|
| 240 | 250 | &otg_sx->vbus_nb); |
|---|
| 241 | | - if (ret < 0) |
|---|
| 251 | + if (ret < 0) { |
|---|
| 242 | 252 | dev_err(ssusb->dev, "failed to register notifier for USB\n"); |
|---|
| 253 | + return ret; |
|---|
| 254 | + } |
|---|
| 243 | 255 | |
|---|
| 244 | 256 | otg_sx->id_nb.notifier_call = ssusb_id_notifier; |
|---|
| 245 | 257 | ret = devm_extcon_register_notifier(ssusb->dev, edev, EXTCON_USB_HOST, |
|---|
| 246 | 258 | &otg_sx->id_nb); |
|---|
| 247 | | - if (ret < 0) |
|---|
| 259 | + if (ret < 0) { |
|---|
| 248 | 260 | dev_err(ssusb->dev, "failed to register notifier for USB-HOST\n"); |
|---|
| 261 | + return ret; |
|---|
| 262 | + } |
|---|
| 249 | 263 | |
|---|
| 250 | 264 | dev_dbg(ssusb->dev, "EXTCON_USB: %d, EXTCON_USB_HOST: %d\n", |
|---|
| 251 | 265 | extcon_get_state(edev, EXTCON_USB), |
|---|
| .. | .. |
|---|
| 266 | 280 | * This is useful in special cases, such as uses TYPE-A receptacle but also |
|---|
| 267 | 281 | * wants to support dual-role mode. |
|---|
| 268 | 282 | */ |
|---|
| 269 | | -static void ssusb_mode_manual_switch(struct ssusb_mtk *ssusb, int to_host) |
|---|
| 283 | +void ssusb_mode_switch(struct ssusb_mtk *ssusb, int to_host) |
|---|
| 270 | 284 | { |
|---|
| 271 | 285 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
|---|
| 272 | 286 | |
|---|
| .. | .. |
|---|
| 279 | 293 | ssusb_set_mailbox(otg_sx, MTU3_ID_FLOAT); |
|---|
| 280 | 294 | ssusb_set_mailbox(otg_sx, MTU3_VBUS_VALID); |
|---|
| 281 | 295 | } |
|---|
| 282 | | -} |
|---|
| 283 | | - |
|---|
| 284 | | -static int ssusb_mode_show(struct seq_file *sf, void *unused) |
|---|
| 285 | | -{ |
|---|
| 286 | | - struct ssusb_mtk *ssusb = sf->private; |
|---|
| 287 | | - |
|---|
| 288 | | - seq_printf(sf, "current mode: %s(%s drd)\n(echo device/host)\n", |
|---|
| 289 | | - ssusb->is_host ? "host" : "device", |
|---|
| 290 | | - ssusb->otg_switch.manual_drd_enabled ? "manual" : "auto"); |
|---|
| 291 | | - |
|---|
| 292 | | - return 0; |
|---|
| 293 | | -} |
|---|
| 294 | | - |
|---|
| 295 | | -static int ssusb_mode_open(struct inode *inode, struct file *file) |
|---|
| 296 | | -{ |
|---|
| 297 | | - return single_open(file, ssusb_mode_show, inode->i_private); |
|---|
| 298 | | -} |
|---|
| 299 | | - |
|---|
| 300 | | -static ssize_t ssusb_mode_write(struct file *file, |
|---|
| 301 | | - const char __user *ubuf, size_t count, loff_t *ppos) |
|---|
| 302 | | -{ |
|---|
| 303 | | - struct seq_file *sf = file->private_data; |
|---|
| 304 | | - struct ssusb_mtk *ssusb = sf->private; |
|---|
| 305 | | - char buf[16]; |
|---|
| 306 | | - |
|---|
| 307 | | - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
|---|
| 308 | | - return -EFAULT; |
|---|
| 309 | | - |
|---|
| 310 | | - if (!strncmp(buf, "host", 4) && !ssusb->is_host) { |
|---|
| 311 | | - ssusb_mode_manual_switch(ssusb, 1); |
|---|
| 312 | | - } else if (!strncmp(buf, "device", 6) && ssusb->is_host) { |
|---|
| 313 | | - ssusb_mode_manual_switch(ssusb, 0); |
|---|
| 314 | | - } else { |
|---|
| 315 | | - dev_err(ssusb->dev, "wrong or duplicated setting\n"); |
|---|
| 316 | | - return -EINVAL; |
|---|
| 317 | | - } |
|---|
| 318 | | - |
|---|
| 319 | | - return count; |
|---|
| 320 | | -} |
|---|
| 321 | | - |
|---|
| 322 | | -static const struct file_operations ssusb_mode_fops = { |
|---|
| 323 | | - .open = ssusb_mode_open, |
|---|
| 324 | | - .write = ssusb_mode_write, |
|---|
| 325 | | - .read = seq_read, |
|---|
| 326 | | - .llseek = seq_lseek, |
|---|
| 327 | | - .release = single_release, |
|---|
| 328 | | -}; |
|---|
| 329 | | - |
|---|
| 330 | | -static int ssusb_vbus_show(struct seq_file *sf, void *unused) |
|---|
| 331 | | -{ |
|---|
| 332 | | - struct ssusb_mtk *ssusb = sf->private; |
|---|
| 333 | | - struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
|---|
| 334 | | - |
|---|
| 335 | | - seq_printf(sf, "vbus state: %s\n(echo on/off)\n", |
|---|
| 336 | | - regulator_is_enabled(otg_sx->vbus) ? "on" : "off"); |
|---|
| 337 | | - |
|---|
| 338 | | - return 0; |
|---|
| 339 | | -} |
|---|
| 340 | | - |
|---|
| 341 | | -static int ssusb_vbus_open(struct inode *inode, struct file *file) |
|---|
| 342 | | -{ |
|---|
| 343 | | - return single_open(file, ssusb_vbus_show, inode->i_private); |
|---|
| 344 | | -} |
|---|
| 345 | | - |
|---|
| 346 | | -static ssize_t ssusb_vbus_write(struct file *file, |
|---|
| 347 | | - const char __user *ubuf, size_t count, loff_t *ppos) |
|---|
| 348 | | -{ |
|---|
| 349 | | - struct seq_file *sf = file->private_data; |
|---|
| 350 | | - struct ssusb_mtk *ssusb = sf->private; |
|---|
| 351 | | - struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
|---|
| 352 | | - char buf[16]; |
|---|
| 353 | | - bool enable; |
|---|
| 354 | | - |
|---|
| 355 | | - if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) |
|---|
| 356 | | - return -EFAULT; |
|---|
| 357 | | - |
|---|
| 358 | | - if (kstrtobool(buf, &enable)) { |
|---|
| 359 | | - dev_err(ssusb->dev, "wrong setting\n"); |
|---|
| 360 | | - return -EINVAL; |
|---|
| 361 | | - } |
|---|
| 362 | | - |
|---|
| 363 | | - ssusb_set_vbus(otg_sx, enable); |
|---|
| 364 | | - |
|---|
| 365 | | - return count; |
|---|
| 366 | | -} |
|---|
| 367 | | - |
|---|
| 368 | | -static const struct file_operations ssusb_vbus_fops = { |
|---|
| 369 | | - .open = ssusb_vbus_open, |
|---|
| 370 | | - .write = ssusb_vbus_write, |
|---|
| 371 | | - .read = seq_read, |
|---|
| 372 | | - .llseek = seq_lseek, |
|---|
| 373 | | - .release = single_release, |
|---|
| 374 | | -}; |
|---|
| 375 | | - |
|---|
| 376 | | -static void ssusb_debugfs_init(struct ssusb_mtk *ssusb) |
|---|
| 377 | | -{ |
|---|
| 378 | | - struct dentry *root; |
|---|
| 379 | | - |
|---|
| 380 | | - root = debugfs_create_dir(dev_name(ssusb->dev), usb_debug_root); |
|---|
| 381 | | - ssusb->dbgfs_root = root; |
|---|
| 382 | | - |
|---|
| 383 | | - debugfs_create_file("mode", 0644, root, ssusb, &ssusb_mode_fops); |
|---|
| 384 | | - debugfs_create_file("vbus", 0644, root, ssusb, &ssusb_vbus_fops); |
|---|
| 385 | | -} |
|---|
| 386 | | - |
|---|
| 387 | | -static void ssusb_debugfs_exit(struct ssusb_mtk *ssusb) |
|---|
| 388 | | -{ |
|---|
| 389 | | - debugfs_remove_recursive(ssusb->dbgfs_root); |
|---|
| 390 | 296 | } |
|---|
| 391 | 297 | |
|---|
| 392 | 298 | void ssusb_set_force_mode(struct ssusb_mtk *ssusb, |
|---|
| .. | .. |
|---|
| 412 | 318 | mtu3_writel(ssusb->ippc_base, SSUSB_U2_CTRL(0), value); |
|---|
| 413 | 319 | } |
|---|
| 414 | 320 | |
|---|
| 321 | +static int ssusb_role_sw_set(struct usb_role_switch *sw, enum usb_role role) |
|---|
| 322 | +{ |
|---|
| 323 | + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
|---|
| 324 | + bool to_host = false; |
|---|
| 325 | + |
|---|
| 326 | + if (role == USB_ROLE_HOST) |
|---|
| 327 | + to_host = true; |
|---|
| 328 | + |
|---|
| 329 | + if (to_host ^ ssusb->is_host) |
|---|
| 330 | + ssusb_mode_switch(ssusb, to_host); |
|---|
| 331 | + |
|---|
| 332 | + return 0; |
|---|
| 333 | +} |
|---|
| 334 | + |
|---|
| 335 | +static enum usb_role ssusb_role_sw_get(struct usb_role_switch *sw) |
|---|
| 336 | +{ |
|---|
| 337 | + struct ssusb_mtk *ssusb = usb_role_switch_get_drvdata(sw); |
|---|
| 338 | + enum usb_role role; |
|---|
| 339 | + |
|---|
| 340 | + role = ssusb->is_host ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
|---|
| 341 | + |
|---|
| 342 | + return role; |
|---|
| 343 | +} |
|---|
| 344 | + |
|---|
| 345 | +static int ssusb_role_sw_register(struct otg_switch_mtk *otg_sx) |
|---|
| 346 | +{ |
|---|
| 347 | + struct usb_role_switch_desc role_sx_desc = { 0 }; |
|---|
| 348 | + struct ssusb_mtk *ssusb = |
|---|
| 349 | + container_of(otg_sx, struct ssusb_mtk, otg_switch); |
|---|
| 350 | + |
|---|
| 351 | + if (!otg_sx->role_sw_used) |
|---|
| 352 | + return 0; |
|---|
| 353 | + |
|---|
| 354 | + role_sx_desc.set = ssusb_role_sw_set; |
|---|
| 355 | + role_sx_desc.get = ssusb_role_sw_get; |
|---|
| 356 | + role_sx_desc.fwnode = dev_fwnode(ssusb->dev); |
|---|
| 357 | + role_sx_desc.driver_data = ssusb; |
|---|
| 358 | + otg_sx->role_sw = usb_role_switch_register(ssusb->dev, &role_sx_desc); |
|---|
| 359 | + |
|---|
| 360 | + return PTR_ERR_OR_ZERO(otg_sx->role_sw); |
|---|
| 361 | +} |
|---|
| 362 | + |
|---|
| 415 | 363 | int ssusb_otg_switch_init(struct ssusb_mtk *ssusb) |
|---|
| 416 | 364 | { |
|---|
| 417 | 365 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
|---|
| 366 | + int ret = 0; |
|---|
| 418 | 367 | |
|---|
| 419 | 368 | INIT_WORK(&otg_sx->id_work, ssusb_id_work); |
|---|
| 420 | 369 | INIT_WORK(&otg_sx->vbus_work, ssusb_vbus_work); |
|---|
| 421 | 370 | |
|---|
| 422 | 371 | if (otg_sx->manual_drd_enabled) |
|---|
| 423 | | - ssusb_debugfs_init(ssusb); |
|---|
| 372 | + ssusb_dr_debugfs_init(ssusb); |
|---|
| 373 | + else if (otg_sx->role_sw_used) |
|---|
| 374 | + ret = ssusb_role_sw_register(otg_sx); |
|---|
| 424 | 375 | else |
|---|
| 425 | | - ssusb_extcon_register(otg_sx); |
|---|
| 376 | + ret = ssusb_extcon_register(otg_sx); |
|---|
| 426 | 377 | |
|---|
| 427 | | - return 0; |
|---|
| 378 | + return ret; |
|---|
| 428 | 379 | } |
|---|
| 429 | 380 | |
|---|
| 430 | 381 | void ssusb_otg_switch_exit(struct ssusb_mtk *ssusb) |
|---|
| 431 | 382 | { |
|---|
| 432 | 383 | struct otg_switch_mtk *otg_sx = &ssusb->otg_switch; |
|---|
| 433 | 384 | |
|---|
| 434 | | - if (otg_sx->manual_drd_enabled) |
|---|
| 435 | | - ssusb_debugfs_exit(ssusb); |
|---|
| 436 | | - |
|---|
| 437 | 385 | cancel_work_sync(&otg_sx->id_work); |
|---|
| 438 | 386 | cancel_work_sync(&otg_sx->vbus_work); |
|---|
| 387 | + usb_role_switch_unregister(otg_sx->role_sw); |
|---|
| 439 | 388 | } |
|---|