usb: typec: ucsi: add support for Cypress CCGx
PATCH v6 2/2 usb: typec: ucsi: add support for Cypress CCGx
Quoted 7 lines · Ajay Gupta
Latest NVIDIA GPU cards have a Cypress CCGx Type-C controller over I2C interface.
This UCSI I2C driver uses I2C bus driver interface for communicating with Type-C controller.
Signed-off-by Ajay Gupta <[email protected]>
Acked-by Heikki Krogerus <[email protected]>
Quoted 440 lines
---
Changes from v1 -> v2
Fixed identation indrivers/usb/typec/ucsi/Kconfig
Changes from v2 -> v3Fixed most of comments from Heikki Rename ucsi_i2c_ccg.c -> ucsi_ccg.cChanges from v3 -> v4
Fixed comments from Andy
Changes from v4 -> v5
Fixed comments from Andy
Changes from v5 -> v6
Fixed review comments from Gregdrivers/usb/typec/ucsi/Kconfig | 10 + drivers/usb/typec/ucsi/Makefile | 2 + drivers/usb/typec/ucsi/ucsi_ccg.c | 382 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 drivers/usb/typec/ucsi/ucsi_ccg.cdrivers/usb/typec/ucsi/Kconfig +10
-0@@ -23,6 +23,16 @@ config TYPEC_UCSI if TYPEC_UCSI +config UCSI_CCG + tristate "UCSI Interface Driver for Cypress CCGx" + depends on I2C + help + This driver enables UCSI support on platforms that expose a + Cypress CCGx Type-C controller over I2C interface. + + To compile the driver as a module, choose M here: the module will be + called ucsi_ccg. + config UCSI_ACPI tristate "UCSI ACPI Interface Driver" depends on ACPIdrivers/usb/typec/ucsi/Makefile +2
-0@@ -8,3 +8,5 @@ typec_ucsi-y := ucsi.o typec_ucsi-$(CONFIG_TRACING) += trace.o obj-$(CONFIG_UCSI_ACPI) += ucsi_acpi.o + +obj-$(CONFIG_UCSI_CCG) += ucsi_ccg.odrivers/usb/typec/ucsi/ucsi_ccg.c new file +382
-0@@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* UCSI driver for Cypress CCGx Type-C controller +* +* Copyright (C) 2017-2018 NVIDIA Corporation. All rights reserved. +* Author: Ajay Gupta <[email protected]> +* +* Some code borrowed from drivers/usb/typec/ucsi/ucsi_acpi.c +*/ +#include <linux/acpi.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/platform_device.h> + +#include <asm/unaligned.h> +#include "ucsi.h" + +struct ucsi_ccg { +struct device *dev; +struct ucsi *ucsi; +struct ucsi_ppm ppm; +struct i2c_client *client; +int irq; +}; + +#define CCGX_I2C_RAB_DEVICE_MODE 0x00 +#define CCGX_I2C_RAB_READ_SILICON_ID 0x2 +#define CCGX_I2C_RAB_INTR_REG 0x06 +#define CCGX_I2C_RAB_FW1_VERSION 0x28 +#define CCGX_I2C_RAB_FW2_VERSION 0x20 +#define CCGX_I2C_RAB_UCSI_CONTROL 0x39 +#define CCGX_I2C_RAB_UCSI_CONTROL_START BIT(0) +#define CCGX_I2C_RAB_UCSI_CONTROL_STOP BIT(1) +#define CCGX_I2C_RAB_RESPONSE_REG 0x7E +#define CCGX_I2C_RAB_UCSI_DATA_BLOCK 0xf000 + +static int ccg_read (struct ucsi_ccg *uc,u16 rab,u8 *data,u32 len) +{ +struct i2c_client *client = uc->client; +unsigned char *buf; +struct i2c_msg *msgs; +u32 rlen, rem_len = len; +int status; + + buf =kzalloc (2 ,GFP_KERNEL ); +if (!buf) +return -ENOMEM ; + + msgs =kcalloc (2 ,sizeof (struct i2c_msg ),GFP_KERNEL ); +if (!msgs) { +kfree (buf); +return -ENOMEM ; + } + + msgs[0 ].addr = client->addr; + msgs[0 ].len =2 ; + msgs[0 ].buf = buf; + msgs[1 ].addr = client->addr; + msgs[1 ].flags =I2C_M_RD ; + +while (rem_len >0 ) { + msgs[1 ].buf = &data[len - rem_len]; + rlen =min_t (u16, rem_len,4 ); + msgs[1 ].len = rlen; +put_unaligned_le16 (rab, buf); + status =i2c_transfer (client->adapter, msgs,2 ); +if (status <0 ) { +dev_err (uc->dev,"i2c_transfer failed %d" , status); +kfree (buf); +kfree (msgs); +return status; + } + rab += rlen; + rem_len -= rlen; + } + +kfree (buf); +kfree (msgs); +return 0 ; +} + +static int ccg_write (struct ucsi_ccg *uc,u16 rab,u8 *data,u32 len) +{ +struct i2c_client *client = uc->client; +unsigned char *buf; +struct i2c_msg *msgs; +int status; + + buf =kzalloc (2 ,GFP_KERNEL ); +if (!buf) +return -ENOMEM ; + + msgs =kcalloc (3 ,sizeof (struct i2c_msg ),GFP_KERNEL ); +if (!msgs) { +kfree (buf); +return -ENOMEM ; + } + + msgs[0 ].addr = client->addr; + msgs[0 ].len =2 ; + msgs[0 ].buf = buf; + msgs[1 ].addr = client->addr; + msgs[1 ].len = len; + msgs[1 ].buf = data; + msgs[2 ].addr = client->addr; + msgs[2 ].flags =I2C_M_STOP ; + +put_unaligned_le16 (rab, buf); + status =i2c_transfer (client->adapter, msgs,3 ); +if (status <0 ) { +dev_err (uc->dev,"i2c_transfer failed %d" , status); +kfree (buf); +kfree (msgs); +return status; + } + +kfree (buf); +kfree (msgs); +return 0 ; +} + +static int ucsi_ccg_init (struct ucsi_ccg *uc) +{ +struct device *dev = uc->dev; +unsigned int count =10 ; +u8 *data; +int status; + + data =kzalloc (64 ,GFP_KERNEL ); +if (!data) +return -ENOMEM ; + +/* +* Selectively issue device reset +* - if RESPONSE register is RESET_COMPLETE, do not issue device reset +* (will cause usb device disconnect / reconnect) +* - if RESPONSE register is not RESET_COMPLETE, issue device reset +* (causes PPC to resync device connect state by re-issuing +* set mux command) +*/ + data[0 ] =0x00 ; + data[1 ] =0x00 ; + + status =ccg_read (uc,CCGX_I2C_RAB_RESPONSE_REG , data,0x2 ); +if (status <0 ) + goto free_mem; + +memset (data,0 ,64 ); + status =ccg_read (uc,CCGX_I2C_RAB_DEVICE_MODE , data,sizeof (data)); +if (status <0 ) + goto free_mem; + +dev_dbg (dev,"Silicon id %2ph" , data +CCGX_I2C_RAB_READ_SILICON_ID ); +dev_dbg (dev,"FW1 version %8ph\n" , data +CCGX_I2C_RAB_FW1_VERSION ); +dev_dbg (dev,"FW2 version %8ph\n" , data +CCGX_I2C_RAB_FW2_VERSION ); + + data[0 ] =0x0 ; + data[1 ] =0x0 ; + status =ccg_read (uc,CCGX_I2C_RAB_RESPONSE_REG , data,0x2 ); +if (status <0 ) + goto free_mem; + + data[0 ] =CCGX_I2C_RAB_UCSI_CONTROL_STOP ; + status =ccg_write (uc,CCGX_I2C_RAB_UCSI_CONTROL , data,0x1 ); +if (status <0 ) + goto free_mem; + + data[0 ] =CCGX_I2C_RAB_UCSI_CONTROL_START ; + status =ccg_write (uc,CCGX_I2C_RAB_UCSI_CONTROL , data,0x1 ); +if (status <0 ) + goto free_mem; + +/* +* Flush CCGx RESPONSE queue by acking interrupts +* - above ucsi control register write will push response +* which must be flushed +* - affects f/w update which reads response register +*/ + data[0 ] =0xff ; +do { + status =ccg_write (uc,CCGX_I2C_RAB_INTR_REG , data,0x1 ); +if (status <0 ) + goto free_mem; + +usleep_range (10000 ,11000 ); + + status =ccg_read (uc,CCGX_I2C_RAB_INTR_REG , data,0x1 ); +if (status <0 ) + goto free_mem; + }while ((data[0 ] !=0x00 ) && count--); + +free_mem: +kfree (data); +return status; +} + +static int ucsi_ccg_send_data (struct ucsi_ccg *uc) +{ +int status; +unsigned char buf[4 ] = { +0x20 ,CCGX_I2C_RAB_UCSI_DATA_BLOCK >>8 , +0x8 ,CCGX_I2C_RAB_UCSI_DATA_BLOCK >>8 , + }; +unsigned char *buf1; +unsigned char *buf2; + + buf1 =kzalloc (16 ,GFP_KERNEL ); +if (!buf1) +return -ENOMEM ; + + buf2 =kzalloc (8 ,GFP_KERNEL ); +if (!buf2) { +kfree (buf1); +return -ENOMEM ; + } + +memcpy (buf1, ((const void *)uc->ppm.data) +0x20 ,16 ); +memcpy (buf2, ((const void *)uc->ppm.data) +0x8 ,8 ); + + status =ccg_write (uc, *(u16 *)buf, buf1,16 ); +if (status <0 ) + goto free_mem; + + status =ccg_write (uc, *(u16 *)(buf +2 ), buf2,8 ); + +free_mem: +kfree (buf1); +kfree (buf2); +return status; +} + +static int ucsi_ccg_recv_data (struct ucsi_ccg *uc) +{ +u8 *ppm = (u8 *)uc->ppm.data; +int status; +unsigned char buf[6 ] = { +0x0 ,CCGX_I2C_RAB_UCSI_DATA_BLOCK >>8 , +0x4 ,CCGX_I2C_RAB_UCSI_DATA_BLOCK >>8 , +0x10 ,CCGX_I2C_RAB_UCSI_DATA_BLOCK >>8 , + }; + + status =ccg_read (uc, *(u16 *)buf, ppm,0x2 ); +if (status <0 ) +return status; + + status =ccg_read (uc, *(u16 *)(buf +2 ), ppm +0x4 ,0x4 ); +if (status <0 ) +return status; + +return ccg_read (uc, *(u16 *)(buf +4 ), ppm +0x10 ,0x10 ); +} + +static int ucsi_ccg_ack_interrupt (struct ucsi_ccg *uc) +{ +int status; +unsigned char buf[2 ] = { +CCGX_I2C_RAB_INTR_REG ,CCGX_I2C_RAB_INTR_REG >>8 }; +unsigned char *buf2; + + buf2 =kzalloc (1 ,GFP_KERNEL ); +if (!buf2) +return -ENOMEM ; + + status =ccg_read (uc, *(u16 *)buf, buf2,0x1 ); +if (status <0 ) + goto free_mem; + + status =ccg_write (uc, *(u16 *)buf, buf2,0x1 ); + +free_mem: +kfree (buf2); +return status; +} + +static int ucsi_ccg_sync (struct ucsi_ppm *ppm) +{ + struct ucsi_ccg *uc = container_of(ppm, struct ucsi_ccg, ppm); +int status; + + status =ucsi_ccg_recv_data (uc); +if (status <0 ) +return status; + +/* ack interrupt to allow next command to run */ +return ucsi_ccg_ack_interrupt (uc); +} + +static int ucsi_ccg_cmd (struct ucsi_ppm *ppm,struct ucsi_control *ctrl) +{ + struct ucsi_ccg *uc = container_of(ppm, struct ucsi_ccg, ppm); + + ppm->data->ctrl.raw_cmd = ctrl->raw_cmd; +return ucsi_ccg_send_data (uc); +} + +static irqreturn_t ccg_irq_handler (int irq,void *data) +{ +struct ucsi_ccg *uc = data; + +ucsi_notify (uc->ucsi); + +return IRQ_HANDLED ; +} + +static int ucsi_ccg_probe (struct i2c_client *client, +const struct i2c_device_id *id) +{ +struct device *dev = &client->dev; +struct ucsi_ccg *uc; +int status; + + uc =devm_kzalloc (dev,sizeof (*uc),GFP_KERNEL ); +if (!uc) +return -ENOMEM ; + + uc->ppm.data =devm_kzalloc (dev,sizeof (struct ucsi_data ),GFP_KERNEL ); +if (!uc->ppm.data) +return -ENOMEM ; + + uc->ppm.cmd = ucsi_ccg_cmd; + uc->ppm.sync = ucsi_ccg_sync; + uc->dev = dev; + uc->client = client; + +/* reset ccg device and initialize ucsi */ + status =ucsi_ccg_init (uc); +if (status <0 ) { +dev_err (uc->dev,"ucsi_ccg_init failed - %d\n" , status); +return status; + } + + uc->irq = client->irq; + + status =devm_request_threaded_irq (dev, uc->irq,NULL , ccg_irq_handler, +IRQF_ONESHOT |IRQF_TRIGGER_HIGH , +dev_name (dev), uc); +if (status <0 ) { +dev_err (uc->dev,"request_threaded_irq failed - %d\n" , status); +return status; + } + + uc->ucsi =ucsi_register_ppm (dev, &uc->ppm); +if (IS_ERR (uc->ucsi)) { +dev_err (uc->dev,"ucsi_register_ppm failed\n" ); +return PTR_ERR (uc->ucsi); + } + +i2c_set_clientdata (client, uc); +return 0 ; +} + +static int ucsi_ccg_remove (struct i2c_client *client) +{ +struct ucsi_ccg *uc =i2c_get_clientdata (client); + +ucsi_unregister_ppm (uc->ucsi); + +return 0 ; +} + +static const struct i2c_device_id ucsi_ccg_device_id[] = { + {"ccgx-ucsi" ,0 }, + {} +}; +MODULE_DEVICE_TABLE (i2c, ucsi_ccg_device_id); + +static struct i2c_driver ucsi_ccg_driver = { + .driver = { + .name ="ucsi_ccg" , + }, + .probe = ucsi_ccg_probe, + .remove = ucsi_ccg_remove, + .id_table = ucsi_ccg_device_id, +}; + +module_i2c_driver (ucsi_ccg_driver); + +MODULE_AUTHOR ("Ajay Gupta <[email protected]>" ); +MODULE_DESCRIPTION ("UCSI driver for Cypress CCGx Type-C controller" ); +MODULE_LICENSE ("GPL v2" );Signature · 1 line
2.7.4