diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2019-07-15 21:10:39 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2019-07-15 21:10:39 -0700 |
commit | 273cbf61c3ddee9574ef1f4959b9bc6db5b24271 (patch) | |
tree | 1eb8a54d416453ad7c6adbf57ab05dce2587a012 /drivers/usb/typec | |
parent | 5fe7b600a116187e10317d83fb56922c4ef6b76d (diff) | |
parent | cc6b9dfb2c5769afeb3335048173c730bdf8dbe1 (diff) | |
download | linux-273cbf61c3ddee9574ef1f4959b9bc6db5b24271.tar.bz2 |
Merge branch 'i2c/for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux
Pull i2c updates from Wolfram Sang:
"New stuff from the I2C world:
- in the core, getting irqs from ACPI is now similar to OF
- new driver for MediaTek MT7621/7628/7688 SoCs
- bcm2835, i801, and tegra drivers got some more attention
- GPIO API cleanups
- cleanups in the core headers
- lots of usual driver updates"
* 'i2c/for-5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/wsa/linux: (74 commits)
i2c: mt7621: Fix platform_no_drv_owner.cocci warnings
i2c: cpm: remove casting dma_alloc
dt-bindings: i2c: sun6i-p2wi: Fix the binding example
dt-bindings: i2c: mv64xxx: Fix the example compatible
i2c: i801: Documentation update
i2c: i801: Add support for Intel Tiger Lake
i2c: i801: Fix PCI ID sorting
dt-bindings: i2c-stm32: document optional dmas
i2c: i2c-stm32f7: Add I2C_SMBUS_I2C_BLOCK_DATA support
i2c: core: Tidy up handling of init_irq
i2c: core: Move ACPI gpio IRQ handling into i2c_acpi_get_irq
i2c: core: Move ACPI IRQ handling to probe time
i2c: acpi: Factor out getting the IRQ from ACPI
i2c: acpi: Use available IRQ helper functions
i2c: core: Allow whole core to use i2c_dev_irq_from_resources
eeprom: at24: modify a comment referring to platform data
dt-bindings: i2c: omap: Add new compatible for J721E SoCs
dt-bindings: i2c: mv64xxx: Add YAML schemas
dt-bindings: i2c: sun6i-p2wi: Add YAML schemas
i2c: mt7621: Add MediaTek MT7621/7628/7688 I2C driver
...
Diffstat (limited to 'drivers/usb/typec')
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 10 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.h | 1 | ||||
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi_ccg.c | 118 |
3 files changed, 125 insertions, 4 deletions
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 7850b851cecd..ba288b964dc8 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -206,7 +206,17 @@ int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl, return ret; } +EXPORT_SYMBOL_GPL(ucsi_send_command); +int ucsi_resume(struct ucsi *ucsi) +{ + struct ucsi_control ctrl; + + /* Restore UCSI notification enable mask after system resume */ + UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_ALL); + return ucsi_send_command(ucsi, &ctrl, NULL, 0); +} +EXPORT_SYMBOL_GPL(ucsi_resume); /* -------------------------------------------------------------------------- */ void ucsi_altmode_update_active(struct ucsi_connector *con) diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h index 1e2981aef629..de87d0b8319d 100644 --- a/drivers/usb/typec/ucsi/ucsi.h +++ b/drivers/usb/typec/ucsi/ucsi.h @@ -430,6 +430,7 @@ int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl, void *retval, size_t size); void ucsi_altmode_update_active(struct ucsi_connector *con); +int ucsi_resume(struct ucsi *ucsi); #if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE) struct typec_altmode * diff --git a/drivers/usb/typec/ucsi/ucsi_ccg.c b/drivers/usb/typec/ucsi/ucsi_ccg.c index bf63074675fc..f7a79a23ebed 100644 --- a/drivers/usb/typec/ucsi/ucsi_ccg.c +++ b/drivers/usb/typec/ucsi/ucsi_ccg.c @@ -14,6 +14,8 @@ #include <linux/module.h> #include <linux/pci.h> #include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> #include <asm/unaligned.h> #include "ucsi.h" @@ -107,12 +109,21 @@ struct version_format { __le16 build; u8 patch; u8 ver; +#define CCG_VERSION_PATCH(x) ((x) << 16) +#define CCG_VERSION(x) ((x) << 24) #define CCG_VERSION_MIN_SHIFT (0) #define CCG_VERSION_MIN_MASK (0xf << CCG_VERSION_MIN_SHIFT) #define CCG_VERSION_MAJ_SHIFT (4) #define CCG_VERSION_MAJ_MASK (0xf << CCG_VERSION_MAJ_SHIFT) } __packed; +/* + * Firmware version 3.1.10 or earlier, built for NVIDIA has known issue + * of missing interrupt when a device is connected for runtime resume + */ +#define CCG_FW_BUILD_NVIDIA (('n' << 8) | 'v') +#define CCG_OLD_FW_VERSION (CCG_VERSION(0x31) | CCG_VERSION_PATCH(10)) + struct version_info { struct version_format base; struct version_format app; @@ -170,6 +181,7 @@ struct ucsi_ccg { struct ccg_dev_info info; /* version info for boot, primary and secondary */ struct version_info version[FW2 + 1]; + u32 fw_version; /* CCG HPI communication flags */ unsigned long flags; #define RESET_PENDING 0 @@ -183,6 +195,8 @@ struct ucsi_ccg { /* fw build with vendor information */ u16 fw_build; + bool run_isr; /* flag to call ISR routine during resume */ + struct work_struct pm_work; }; static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) @@ -210,6 +224,19 @@ static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) if (quirks && quirks->max_read_len) max_read_len = quirks->max_read_len; + if (uc->fw_build == CCG_FW_BUILD_NVIDIA && + uc->fw_version <= CCG_OLD_FW_VERSION) { + mutex_lock(&uc->lock); + /* + * Do not schedule pm_work to run ISR in + * ucsi_ccg_runtime_resume() after pm_runtime_get_sync() + * since we are already in ISR path. + */ + uc->run_isr = false; + mutex_unlock(&uc->lock); + } + + pm_runtime_get_sync(uc->dev); while (rem_len > 0) { msgs[1].buf = &data[len - rem_len]; rlen = min_t(u16, rem_len, max_read_len); @@ -218,12 +245,14 @@ static int ccg_read(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); if (status < 0) { dev_err(uc->dev, "i2c_transfer failed %d\n", status); + pm_runtime_put_sync(uc->dev); return status; } rab += rlen; rem_len -= rlen; } + pm_runtime_put_sync(uc->dev); return 0; } @@ -249,13 +278,28 @@ static int ccg_write(struct ucsi_ccg *uc, u16 rab, u8 *data, u32 len) msgs[0].len = len + sizeof(rab); msgs[0].buf = buf; + if (uc->fw_build == CCG_FW_BUILD_NVIDIA && + uc->fw_version <= CCG_OLD_FW_VERSION) { + mutex_lock(&uc->lock); + /* + * Do not schedule pm_work to run ISR in + * ucsi_ccg_runtime_resume() after pm_runtime_get_sync() + * since we are already in ISR path. + */ + uc->run_isr = false; + mutex_unlock(&uc->lock); + } + + pm_runtime_get_sync(uc->dev); status = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); if (status < 0) { dev_err(uc->dev, "i2c_transfer failed %d\n", status); + pm_runtime_put_sync(uc->dev); kfree(buf); return status; } + pm_runtime_put_sync(uc->dev); kfree(buf); return 0; } @@ -375,6 +419,13 @@ static irqreturn_t ccg_irq_handler(int irq, void *data) return IRQ_HANDLED; } +static void ccg_pm_workaround_work(struct work_struct *pm_work) +{ + struct ucsi_ccg *uc = container_of(pm_work, struct ucsi_ccg, pm_work); + + ucsi_notify(uc->ucsi); +} + static int get_fw_info(struct ucsi_ccg *uc) { int err; @@ -384,6 +435,9 @@ static int get_fw_info(struct ucsi_ccg *uc) if (err < 0) return err; + uc->fw_version = CCG_VERSION(uc->version[FW2].app.ver) | + CCG_VERSION_PATCH(uc->version[FW2].app.patch); + err = ccg_read(uc, CCGX_RAB_DEVICE_MODE, (u8 *)(&uc->info), sizeof(uc->info)); if (err < 0) @@ -732,11 +786,12 @@ static bool ccg_check_fw_version(struct ucsi_ccg *uc, const char *fw_name, } /* compare input version with FWCT version */ - cur_version = le16_to_cpu(app->build) | app->patch << 16 | - app->ver << 24; + cur_version = le16_to_cpu(app->build) | CCG_VERSION_PATCH(app->patch) | + CCG_VERSION(app->ver); - new_version = le16_to_cpu(fw_cfg.app.build) | fw_cfg.app.patch << 16 | - fw_cfg.app.ver << 24; + new_version = le16_to_cpu(fw_cfg.app.build) | + CCG_VERSION_PATCH(fw_cfg.app.patch) | + CCG_VERSION(fw_cfg.app.ver); if (!ccg_check_vendor_version(uc, app, &fw_cfg)) goto out_release_firmware; @@ -1078,8 +1133,10 @@ static int ucsi_ccg_probe(struct i2c_client *client, uc->ppm.sync = ucsi_ccg_sync; uc->dev = dev; uc->client = client; + uc->run_isr = true; mutex_init(&uc->lock); INIT_WORK(&uc->work, ccg_update_firmware); + INIT_WORK(&uc->pm_work, ccg_pm_workaround_work); /* Only fail FW flashing when FW build information is not provided */ status = device_property_read_u16(dev, "ccgx,firmware-build", @@ -1136,6 +1193,10 @@ static int ucsi_ccg_probe(struct i2c_client *client, if (status) dev_err(uc->dev, "cannot create sysfs group: %d\n", status); + pm_runtime_set_active(uc->dev); + pm_runtime_enable(uc->dev); + pm_runtime_idle(uc->dev); + return 0; } @@ -1143,8 +1204,10 @@ static int ucsi_ccg_remove(struct i2c_client *client) { struct ucsi_ccg *uc = i2c_get_clientdata(client); + cancel_work_sync(&uc->pm_work); cancel_work_sync(&uc->work); ucsi_unregister_ppm(uc->ucsi); + pm_runtime_disable(uc->dev); free_irq(uc->irq, uc); sysfs_remove_group(&uc->dev->kobj, &ucsi_ccg_attr_group); @@ -1157,9 +1220,56 @@ static const struct i2c_device_id ucsi_ccg_device_id[] = { }; MODULE_DEVICE_TABLE(i2c, ucsi_ccg_device_id); +static int ucsi_ccg_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ucsi_ccg *uc = i2c_get_clientdata(client); + + return ucsi_resume(uc->ucsi); +} + +static int ucsi_ccg_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int ucsi_ccg_runtime_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ucsi_ccg *uc = i2c_get_clientdata(client); + bool schedule = true; + + /* + * Firmware version 3.1.10 or earlier, built for NVIDIA has known issue + * of missing interrupt when a device is connected for runtime resume. + * Schedule a work to call ISR as a workaround. + */ + if (uc->fw_build == CCG_FW_BUILD_NVIDIA && + uc->fw_version <= CCG_OLD_FW_VERSION) { + mutex_lock(&uc->lock); + if (!uc->run_isr) { + uc->run_isr = true; + schedule = false; + } + mutex_unlock(&uc->lock); + + if (schedule) + schedule_work(&uc->pm_work); + } + + return 0; +} + +static const struct dev_pm_ops ucsi_ccg_pm = { + .resume = ucsi_ccg_resume, + .runtime_suspend = ucsi_ccg_runtime_suspend, + .runtime_resume = ucsi_ccg_runtime_resume, +}; + static struct i2c_driver ucsi_ccg_driver = { .driver = { .name = "ucsi_ccg", + .pm = &ucsi_ccg_pm, }, .probe = ucsi_ccg_probe, .remove = ucsi_ccg_remove, |