diff options
| author | Wolfram Sang <wsa+renesas@sang-engineering.com> | 2026-02-19 15:11:15 +0100 |
|---|---|---|
| committer | Wolfram Sang <wsa+renesas@sang-engineering.com> | 2026-02-19 15:11:15 +0100 |
| commit | 709cc48d3d01facaeb1eec3d93e1e1fb2fb21717 (patch) | |
| tree | eae2e7ab298b877a62bada827433843f6de682db | |
| parent | 2b7a25df823dc7d8f56f8ce7c2d2dac391cea9c2 (diff) | |
| parent | 079a015b5a630a87632f5585247d1ff7fd80086b (diff) | |
Merge branch 'i2c/i2c-host-2' of git://git.kernel.org/pub/scm/linux/kernel/git/andi.shyti/linux into i2c/for-mergewindow
| -rw-r--r-- | Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml | 2 | ||||
| -rw-r--r-- | Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml | 100 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-amd8111.c | 30 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-designware-amdisp.c | 13 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-designware-common.c | 20 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-designware-core.h | 3 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-designware-master.c | 162 | ||||
| -rw-r--r-- | drivers/i2c/busses/i2c-designware-platdrv.c | 42 |
8 files changed, 266 insertions, 106 deletions
diff --git a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml index a3fe1eea6aec..399a09409e07 100644 --- a/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml +++ b/Documentation/devicetree/bindings/i2c/qcom,i2c-cci.yaml @@ -28,6 +28,7 @@ properties: - enum: - qcom,kaanapali-cci - qcom,qcm2290-cci + - qcom,qcs8300-cci - qcom,sa8775p-cci - qcom,sc7280-cci - qcom,sc8280xp-cci @@ -133,6 +134,7 @@ allOf: enum: - qcom,kaanapali-cci - qcom,qcm2290-cci + - qcom,qcs8300-cci - qcom,sm8750-cci then: properties: diff --git a/Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml b/Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml new file mode 100644 index 000000000000..a204adfe57b3 --- /dev/null +++ b/Documentation/devicetree/bindings/i2c/silabs,cp2112.yaml @@ -0,0 +1,100 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/i2c/silabs,cp2112.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: CP2112 HID USB to SMBus/I2C Bridge + +maintainers: + - Danny Kaehn <danny.kaehn@plexus.com> + +description: + The CP2112 is a USB HID device which includes an integrated I2C controller + and 8 GPIO pins. Its GPIO pins can each be configured as inputs, open-drain + outputs, or push-pull outputs. + +properties: + compatible: + const: usb10c4,ea90 + + reg: + maxItems: 1 + description: The USB port number + + interrupt-controller: true + "#interrupt-cells": + const: 2 + + gpio-controller: true + "#gpio-cells": + const: 2 + + gpio-line-names: + minItems: 1 + maxItems: 8 + + i2c: + description: The SMBus/I2C controller node for the CP2112 + $ref: /schemas/i2c/i2c-controller.yaml# + unevaluatedProperties: false + + properties: + clock-frequency: + minimum: 10000 + default: 100000 + maximum: 400000 + +patternProperties: + "-hog(-[0-9]+)?$": + type: object + + required: + - gpio-hog + +required: + - compatible + - reg + +additionalProperties: false + +examples: + - | + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/gpio/gpio.h> + + usb { + #address-cells = <1>; + #size-cells = <0>; + + cp2112: device@1 { + compatible = "usb10c4,ea90"; + reg = <1>; + + gpio-controller; + interrupt-controller; + #interrupt-cells = <2>; + #gpio-cells = <2>; + gpio-line-names = "CP2112_SDA", "CP2112_SCL", "TEST2", + "TEST3","TEST4", "TEST5", "TEST6"; + + fan-rst-hog { + gpio-hog; + gpios = <7 GPIO_ACTIVE_HIGH>; + output-high; + line-name = "FAN_RST"; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + sda-gpios = <&cp2112 0 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; + scl-gpios = <&cp2112 1 (GPIO_ACTIVE_HIGH | GPIO_OPEN_DRAIN)>; + + temp@48 { + compatible = "national,lm75"; + reg = <0x48>; + }; + }; + }; + }; diff --git a/drivers/i2c/busses/i2c-amd8111.c b/drivers/i2c/busses/i2c-amd8111.c index 42a9b1221065..dd9ac4bb6704 100644 --- a/drivers/i2c/busses/i2c-amd8111.c +++ b/drivers/i2c/busses/i2c-amd8111.c @@ -17,7 +17,7 @@ #include <linux/io.h> MODULE_LICENSE("GPL"); -MODULE_AUTHOR ("Vojtech Pavlik <vojtech@suse.cz>"); +MODULE_AUTHOR("Vojtech Pavlik <vojtech@suse.cz>"); MODULE_DESCRIPTION("AMD8111 SMBus 2.0 driver"); struct amd_smbus { @@ -417,7 +417,7 @@ static const struct pci_device_id amd8111_ids[] = { { 0, } }; -MODULE_DEVICE_TABLE (pci, amd8111_ids); +MODULE_DEVICE_TABLE(pci, amd8111_ids); static int amd8111_probe(struct pci_dev *dev, const struct pci_device_id *id) { @@ -427,7 +427,7 @@ static int amd8111_probe(struct pci_dev *dev, const struct pci_device_id *id) if (!(pci_resource_flags(dev, 0) & IORESOURCE_IO)) return -ENODEV; - smbus = kzalloc(sizeof(struct amd_smbus), GFP_KERNEL); + smbus = devm_kzalloc(&dev->dev, sizeof(struct amd_smbus), GFP_KERNEL); if (!smbus) return -ENOMEM; @@ -436,19 +436,15 @@ static int amd8111_probe(struct pci_dev *dev, const struct pci_device_id *id) smbus->size = pci_resource_len(dev, 0); error = acpi_check_resource_conflict(&dev->resource[0]); - if (error) { - error = -ENODEV; - goto out_kfree; - } + if (error) + return -ENODEV; - if (!request_region(smbus->base, smbus->size, amd8111_driver.name)) { - error = -EBUSY; - goto out_kfree; - } + if (!devm_request_region(&dev->dev, smbus->base, smbus->size, amd8111_driver.name)) + return -EBUSY; smbus->adapter.owner = THIS_MODULE; snprintf(smbus->adapter.name, sizeof(smbus->adapter.name), - "SMBus2 AMD8111 adapter at %04x", smbus->base); + "SMBus2 AMD8111 adapter at %04x", smbus->base); smbus->adapter.class = I2C_CLASS_HWMON; smbus->adapter.algo = &smbus_algorithm; smbus->adapter.algo_data = smbus; @@ -459,16 +455,10 @@ static int amd8111_probe(struct pci_dev *dev, const struct pci_device_id *id) pci_write_config_dword(smbus->dev, AMD_PCI_MISC, 0); error = i2c_add_adapter(&smbus->adapter); if (error) - goto out_release_region; + return error; pci_set_drvdata(dev, smbus); return 0; - - out_release_region: - release_region(smbus->base, smbus->size); - out_kfree: - kfree(smbus); - return error; } static void amd8111_remove(struct pci_dev *dev) @@ -476,8 +466,6 @@ static void amd8111_remove(struct pci_dev *dev) struct amd_smbus *smbus = pci_get_drvdata(dev); i2c_del_adapter(&smbus->adapter); - release_region(smbus->base, smbus->size); - kfree(smbus); } static struct pci_driver amd8111_driver = { diff --git a/drivers/i2c/busses/i2c-designware-amdisp.c b/drivers/i2c/busses/i2c-designware-amdisp.c index ec9259dd2a4f..c48728ad9f6f 100644 --- a/drivers/i2c/busses/i2c-designware-amdisp.c +++ b/drivers/i2c/busses/i2c-designware-amdisp.c @@ -18,9 +18,6 @@ static void amd_isp_dw_i2c_plat_pm_cleanup(struct dw_i2c_dev *i2c_dev) { pm_runtime_disable(i2c_dev->dev); - - if (i2c_dev->shared_with_punit) - pm_runtime_put_noidle(i2c_dev->dev); } static inline u32 amd_isp_dw_i2c_get_clk_rate(struct dw_i2c_dev *i2c_dev) @@ -79,9 +76,6 @@ static int amd_isp_dw_i2c_plat_probe(struct platform_device *pdev) device_enable_async_suspend(&pdev->dev); - if (isp_i2c_dev->shared_with_punit) - pm_runtime_get_noresume(&pdev->dev); - pm_runtime_enable(&pdev->dev); pm_runtime_get_sync(&pdev->dev); @@ -130,9 +124,6 @@ static int amd_isp_dw_i2c_plat_runtime_suspend(struct device *dev) { struct dw_i2c_dev *i_dev = dev_get_drvdata(dev); - if (i_dev->shared_with_punit) - return 0; - i2c_dw_disable(i_dev); i2c_dw_prepare_clk(i_dev, false); @@ -161,9 +152,7 @@ static int amd_isp_dw_i2c_plat_runtime_resume(struct device *dev) if (!i_dev) return -ENODEV; - if (!i_dev->shared_with_punit) - i2c_dw_prepare_clk(i_dev, true); - + i2c_dw_prepare_clk(i_dev, true); i2c_dw_init(i_dev); return 0; diff --git a/drivers/i2c/busses/i2c-designware-common.c b/drivers/i2c/busses/i2c-designware-common.c index 64654dabbb21..4dc57fd56170 100644 --- a/drivers/i2c/busses/i2c-designware-common.c +++ b/drivers/i2c/busses/i2c-designware-common.c @@ -492,6 +492,12 @@ int i2c_dw_fw_parse_and_configure(struct dw_i2c_dev *dev) dev->clk_freq_optimized = device_property_read_bool(device, "snps,clk-freq-optimized"); + /* Mobileye controllers do not hold the clock on empty FIFO */ + if (device_is_compatible(device, "mobileye,eyeq6lplus-i2c")) + dev->emptyfifo_hold_master = false; + else + dev->emptyfifo_hold_master = true; + i2c_dw_adjust_bus_speed(dev); if (is_of_node(fwnode)) @@ -918,6 +924,20 @@ int i2c_dw_probe(struct dw_i2c_dev *dev) else irq_flags = IRQF_SHARED | IRQF_COND_SUSPEND; + /* + * The first writing to TX FIFO buffer causes transmission start. + * If IC_EMPTYFIFO_HOLD_MASTER_EN is not set, when TX FIFO gets + * empty, I2C controller finishes the transaction. If writing to + * FIFO is interrupted, FIFO can get empty and the transaction will + * be finished prematurely. FIFO buffer is filled in IRQ handler, + * but in PREEMPT_RT kernel IRQ handler by default is executed + * in thread that can be preempted with another higher priority + * thread or an interrupt. So, IRQF_NO_THREAD flag is required in + * order to prevent any preemption when filling the FIFO. + */ + if (!dev->emptyfifo_hold_master) + irq_flags |= IRQF_NO_THREAD; + ret = i2c_dw_acquire_lock(dev); if (ret) return ret; diff --git a/drivers/i2c/busses/i2c-designware-core.h b/drivers/i2c/busses/i2c-designware-core.h index a49263a36023..9d8d104cc391 100644 --- a/drivers/i2c/busses/i2c-designware-core.h +++ b/drivers/i2c/busses/i2c-designware-core.h @@ -260,6 +260,8 @@ struct reset_control; * @clk_freq_optimized: if this value is true, it means the hardware reduces * its internal clock frequency by reducing the internal latency required * to generate the high period and low period of SCL line. + * @emptyfifo_hold_master: true if the controller acting as master holds + * the clock when the Tx FIFO is empty instead of emitting a stop. * * HCNT and LCNT parameters can be used if the platform knows more accurate * values than the one computed based only on the input clock frequency. @@ -318,6 +320,7 @@ struct dw_i2c_dev { struct i2c_bus_recovery_info rinfo; u32 bus_capacitance_pF; bool clk_freq_optimized; + bool emptyfifo_hold_master; }; #define ACCESS_INTR_MASK BIT(0) diff --git a/drivers/i2c/busses/i2c-designware-master.c b/drivers/i2c/busses/i2c-designware-master.c index 8ca254cbb2f8..de929b91d5ea 100644 --- a/drivers/i2c/busses/i2c-designware-master.c +++ b/drivers/i2c/busses/i2c-designware-master.c @@ -296,8 +296,8 @@ static int amd_i2c_dw_xfer_quirk(struct dw_i2c_dev *dev, struct i2c_msg *msgs, i u8 *tx_buf; unsigned int val; - ACQUIRE(pm_runtime_active_auto_try, pm)(dev->dev); - if (ACQUIRE_ERR(pm_runtime_active_auto_try, &pm)) + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO; /* @@ -377,7 +377,6 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) struct i2c_msg *msgs = dev->msgs; u32 intr_mask; int tx_limit, rx_limit; - u32 addr = msgs[dev->msg_write_idx].addr; u32 buf_len = dev->tx_buf_len; u8 *buf = dev->tx_buf; bool need_restart = false; @@ -388,18 +387,6 @@ i2c_dw_xfer_msg(struct dw_i2c_dev *dev) for (; dev->msg_write_idx < dev->msgs_num; dev->msg_write_idx++) { u32 flags = msgs[dev->msg_write_idx].flags; - /* - * If target address has changed, we need to - * reprogram the target address in the I2C - * adapter when we are done with this transfer. - */ - if (msgs[dev->msg_write_idx].addr != addr) { - dev_err(dev->dev, - "%s: invalid target address\n", __func__); - dev->msg_err = -EINVAL; - break; - } - if (!(dev->status & STATUS_WRITE_IN_PROGRESS)) { /* new i2c_msg */ buf = msgs[dev->msg_write_idx].buf; @@ -665,6 +652,14 @@ static void i2c_dw_process_transfer(struct dw_i2c_dev *dev, unsigned int stat) if (stat & DW_IC_INTR_TX_EMPTY) i2c_dw_xfer_msg(dev); + /* Abort if we detect a STOP in the middle of a read or a write */ + if ((stat & DW_IC_INTR_STOP_DET) && + (dev->status & (STATUS_READ_IN_PROGRESS | STATUS_WRITE_IN_PROGRESS))) { + dev_err(dev->dev, "spurious STOP detected\n"); + dev->rx_outstanding = 0; + dev->msg_err = -EIO; + } + /* * No need to modify or disable the interrupt mask here. * i2c_dw_xfer_msg() will take care of it according to @@ -746,17 +741,15 @@ static int i2c_dw_wait_transfer(struct dw_i2c_dev *dev) } /* - * Prepare controller for a transaction and call i2c_dw_xfer_msg. + * Prepare controller for a transaction, start the transfer of the @msgs + * and wait for completion, either a STOP or a error. + * Return: 0 or a negative error code. */ static int -i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) +__i2c_dw_xfer_one_part(struct dw_i2c_dev *dev, struct i2c_msg *msgs, size_t num) { int ret; - dev_dbg(dev->dev, "%s: msgs: %d\n", __func__, num); - - pm_runtime_get_sync(dev->dev); - reinit_completion(&dev->cmd_complete); dev->msgs = msgs; dev->msgs_num = num; @@ -768,13 +761,9 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) dev->abort_source = 0; dev->rx_outstanding = 0; - ret = i2c_dw_acquire_lock(dev); - if (ret) - goto done_nolock; - ret = i2c_dw_wait_bus_not_busy(dev); if (ret < 0) - goto done; + return ret; /* Start the transfers */ i2c_dw_xfer_init(dev); @@ -786,7 +775,7 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) /* i2c_dw_init() implicitly disables the adapter */ i2c_recover_bus(&dev->adapter); i2c_dw_init(dev); - goto done; + return ret; } /* @@ -809,38 +798,117 @@ i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) */ __i2c_dw_disable_nowait(dev); - if (dev->msg_err) { - ret = dev->msg_err; - goto done; - } + if (dev->msg_err) + return dev->msg_err; /* No error */ - if (likely(!dev->cmd_err && !dev->status)) { - ret = num; - goto done; - } + if (likely(!dev->cmd_err && !dev->status)) + return 0; /* We have an error */ - if (dev->cmd_err == DW_IC_ERR_TX_ABRT) { - ret = i2c_dw_handle_tx_abort(dev); - goto done; - } + if (dev->cmd_err == DW_IC_ERR_TX_ABRT) + return i2c_dw_handle_tx_abort(dev); if (dev->status) dev_err(dev->dev, "transfer terminated early - interrupt latency too high?\n"); - ret = -EIO; + return -EIO; +} + +/* + * Verify that the message at index @idx can be processed as part + * of a single transaction. The @msgs array contains the messages + * of the transaction. The message is checked against its predecessor + * to ensure that it respects the limitation of the controller. + * Return: true if the message can be processed, false otherwise. + */ +static bool +i2c_dw_msg_is_valid(struct dw_i2c_dev *dev, const struct i2c_msg *msgs, size_t idx) +{ + /* + * The first message of a transaction is valid, + * no constraints from a previous message. + */ + if (!idx) + return true; + + /* + * We cannot change the target address during a transaction, so make + * sure the address is identical to the one of the previous message. + */ + if (msgs[idx - 1].addr != msgs[idx].addr) { + dev_err(dev->dev, "invalid target address\n"); + return false; + } + + /* + * Make sure we don't need explicit RESTART between two messages + * in the same direction for controllers that cannot emit them. + */ + if (!dev->emptyfifo_hold_master && + (msgs[idx - 1].flags & I2C_M_RD) == (msgs[idx].flags & I2C_M_RD)) { + dev_err(dev->dev, "cannot emit RESTART\n"); + return false; + } + + return true; +} + +static int +i2c_dw_xfer_common(struct dw_i2c_dev *dev, struct i2c_msg msgs[], int num) +{ + struct i2c_msg *msgs_part; + size_t cnt; + int ret; + + dev_dbg(dev->dev, "msgs: %d\n", num); + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev->dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + ret = i2c_dw_acquire_lock(dev); + if (ret) + return ret; + + /* + * If the I2C_M_STOP is present in some the messages, + * we do one transaction for each part up to the STOP. + */ + for (msgs_part = msgs; msgs_part < msgs + num; msgs_part += cnt) { + /* + * Count the messages in a transaction, up to a STOP or + * the end of the msgs. The last if below guarantees that + * we check all messages and that msg_parts and cnt are + * in-bounds of msgs and num. + */ + for (cnt = 1; ; cnt++) { + if (!i2c_dw_msg_is_valid(dev, msgs_part, cnt - 1)) { + ret = -EINVAL; + break; + } + + if ((msgs_part[cnt - 1].flags & I2C_M_STOP) || + (msgs_part + cnt == msgs + num)) + break; + } + if (ret < 0) + break; + + /* transfer one part up to a STOP */ + ret = __i2c_dw_xfer_one_part(dev, msgs_part, cnt); + if (ret < 0) + break; + } -done: i2c_dw_set_mode(dev, DW_IC_SLAVE); i2c_dw_release_lock(dev); -done_nolock: - pm_runtime_put_autosuspend(dev->dev); - - return ret; + if (ret < 0) + return ret; + return num; } int i2c_dw_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) @@ -859,6 +927,10 @@ void i2c_dw_configure_master(struct dw_i2c_dev *dev) dev->functionality |= I2C_FUNC_10BIT_ADDR | DW_IC_DEFAULT_FUNCTIONALITY; + /* amd_i2c_dw_xfer_quirk() does not implement protocol mangling */ + if ((dev->flags & MODEL_MASK) != MODEL_AMD_NAVI_GPU) + dev->functionality |= I2C_FUNC_PROTOCOL_MANGLING; + dev->master_cfg = DW_IC_CON_MASTER | DW_IC_CON_SLAVE_DISABLE | DW_IC_CON_RESTART_EN; diff --git a/drivers/i2c/busses/i2c-designware-platdrv.c b/drivers/i2c/busses/i2c-designware-platdrv.c index 4e6fe3b55322..426ffec06e22 100644 --- a/drivers/i2c/busses/i2c-designware-platdrv.c +++ b/drivers/i2c/busses/i2c-designware-platdrv.c @@ -160,40 +160,32 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) if (ret) return ret; - dev->rst = devm_reset_control_get_optional_exclusive(device, NULL); + dev->rst = devm_reset_control_get_optional_exclusive_deasserted(device, NULL); if (IS_ERR(dev->rst)) return dev_err_probe(device, PTR_ERR(dev->rst), "failed to acquire reset\n"); - reset_control_deassert(dev->rst); - ret = i2c_dw_fw_parse_and_configure(dev); if (ret) - goto exit_reset; + return ret; ret = i2c_dw_probe_lock_support(dev); - if (ret) { - dev_err_probe(device, ret, "failed to probe lock support\n"); - goto exit_reset; - } + if (ret) + return dev_err_probe(device, ret, "failed to probe lock support\n"); i2c_dw_configure(dev); /* Optional interface clock */ dev->pclk = devm_clk_get_optional(device, "pclk"); - if (IS_ERR(dev->pclk)) { - ret = dev_err_probe(device, PTR_ERR(dev->pclk), "failed to acquire pclk\n"); - goto exit_reset; - } + if (IS_ERR(dev->pclk)) + return dev_err_probe(device, PTR_ERR(dev->pclk), "failed to acquire pclk\n"); dev->clk = devm_clk_get_optional(device, NULL); - if (IS_ERR(dev->clk)) { - ret = dev_err_probe(device, PTR_ERR(dev->clk), "failed to acquire clock\n"); - goto exit_reset; - } + if (IS_ERR(dev->clk)) + return dev_err_probe(device, PTR_ERR(dev->clk), "failed to acquire clock\n"); ret = i2c_dw_prepare_clk(dev, true); if (ret) - goto exit_reset; + return ret; if (dev->clk) { struct i2c_timings *t = &dev->timings; @@ -233,16 +225,11 @@ static int dw_i2c_plat_probe(struct platform_device *pdev) pm_runtime_enable(device); ret = i2c_dw_probe(dev); - if (ret) - goto exit_probe; - - return ret; + if (ret) { + dw_i2c_plat_pm_cleanup(dev); + i2c_dw_prepare_clk(dev, false); + } -exit_probe: - dw_i2c_plat_pm_cleanup(dev); - i2c_dw_prepare_clk(dev, false); -exit_reset: - reset_control_assert(dev->rst); return ret; } @@ -262,11 +249,10 @@ static void dw_i2c_plat_remove(struct platform_device *pdev) dw_i2c_plat_pm_cleanup(dev); i2c_dw_prepare_clk(dev, false); - - reset_control_assert(dev->rst); } static const struct of_device_id dw_i2c_of_match[] = { + { .compatible = "mobileye,eyeq6lplus-i2c" }, { .compatible = "mscc,ocelot-i2c" }, { .compatible = "snps,designware-i2c" }, {} |
