// SPDX-License-Identifier: GPL-2.0+ /* * (C) Copyright 2009 * Vipin Kumar, ST Micoelectronics, vipin.kumar@st.com. * Copyright 2019 Google Inc */ #include #include #include #include #include #include #include #include #include #include #include "designware_i2c.h" enum { VANILLA = 0, /* standard I2C with no tweaks */ INTEL_APL, /* Apollo Lake I2C */ }; /* BayTrail HCNT/LCNT/SDA hold time */ static struct dw_scl_sda_cfg byt_config = { .ss_hcnt = 0x200, .fs_hcnt = 0x55, .ss_lcnt = 0x200, .fs_lcnt = 0x99, .sda_hold = 0x6, }; /* Have a weak function for now - possibly should be a new uclass */ __weak void lpss_reset_release(void *regs); static int designware_i2c_pci_of_to_plat(struct udevice *dev) { struct dw_i2c *priv = dev_get_priv(dev); if (spl_phase() < PHASE_SPL) { u32 base; int ret; ret = dev_read_u32(dev, "early-regs", &base); if (ret) return log_msg_ret("early-regs", ret); /* Set i2c base address */ dm_pci_write_config32(dev, PCI_BASE_ADDRESS_0, base); /* Enable memory access and bus master */ dm_pci_write_config32(dev, PCI_COMMAND, PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER); } if (spl_phase() < PHASE_BOARD_F) { /* Handle early, fixed mapping into a different address space */ priv->regs = (struct i2c_regs *)dm_pci_read_bar32(dev, 0); } else { priv->regs = (struct i2c_regs *) dm_pci_map_bar(dev, PCI_BASE_ADDRESS_0, PCI_REGION_MEM); } if (!priv->regs) return -EINVAL; /* Save base address from PCI BAR */ if (IS_ENABLED(CONFIG_INTEL_BAYTRAIL)) /* Use BayTrail specific timing values */ priv->scl_sda_cfg = &byt_config; if (dev_get_driver_data(dev) == INTEL_APL) priv->has_spk_cnt = true; return designware_i2c_of_to_plat(dev); } static int designware_i2c_pci_probe(struct udevice *dev) { struct dw_i2c *priv = dev_get_priv(dev); if (dev_get_driver_data(dev) == INTEL_APL) { /* Ensure controller is in D0 state */ lpss_set_power_state(dev, STATE_D0); lpss_reset_release(priv->regs); } return designware_i2c_probe(dev); } static int designware_i2c_pci_bind(struct udevice *dev) { struct uclass *uc; char name[20]; int ret; if (dev_of_valid(dev)) return 0; /* * Create a unique device name for PCI type devices * ToDo: * Setting req_seq in the driver is probably not recommended. * But without a DT alias the number is not configured. And * using this driver is impossible for PCIe I2C devices. * This can be removed, once a better (correct) way for this * is found and implemented. * * TODO(sjg@chromium.org): Perhaps if uclasses had platdata this would * be possible. We cannot use static data in drivers since they may be * used in SPL or before relocation. */ ret = uclass_get(UCLASS_I2C, &uc); if (ret) return ret; dev->req_seq = uclass_find_next_free_req_seq(uc); sprintf(name, "i2c_designware#%u", dev->req_seq); device_set_name(dev, name); return 0; } /* * Write ACPI object to describe speed configuration. * * ACPI Object: Name ("xxxx", Package () { scl_lcnt, scl_hcnt, sda_hold } * * SSCN: I2C_SPEED_STANDARD * FMCN: I2C_SPEED_FAST * FPCN: I2C_SPEED_FAST_PLUS * HSCN: I2C_SPEED_HIGH */ static void dw_i2c_acpi_write_speed_config(struct acpi_ctx *ctx, struct dw_i2c_speed_config *config) { switch (config->speed_mode) { case IC_SPEED_MODE_HIGH: acpigen_write_name(ctx, "HSCN"); break; case IC_SPEED_MODE_FAST_PLUS: acpigen_write_name(ctx, "FPCN"); break; case IC_SPEED_MODE_FAST: acpigen_write_name(ctx, "FMCN"); break; case IC_SPEED_MODE_STANDARD: default: acpigen_write_name(ctx, "SSCN"); } /* Package () { scl_lcnt, scl_hcnt, sda_hold } */ acpigen_write_package(ctx, 3); acpigen_write_word(ctx, config->scl_hcnt); acpigen_write_word(ctx, config->scl_lcnt); acpigen_write_dword(ctx, config->sda_hold); acpigen_pop_len(ctx); } /* * Generate I2C timing information into the SSDT for the OS driver to consume, * optionally applying override values provided by the caller. */ static int dw_i2c_acpi_fill_ssdt(const struct udevice *dev, struct acpi_ctx *ctx) { struct dw_i2c_speed_config config; char path[ACPI_PATH_MAX]; u32 speeds[4]; uint speed; int size; int ret; /* If no device-tree node, ignore this since we assume it isn't used */ if (!dev_of_valid(dev)) return 0; ret = acpi_device_path(dev, path, sizeof(path)); if (ret) return log_msg_ret("path", ret); size = dev_read_size(dev, "i2c,speeds"); if (size < 0) return log_msg_ret("i2c,speeds", -EINVAL); size /= sizeof(u32); if (size > ARRAY_SIZE(speeds)) return log_msg_ret("array", -E2BIG); ret = dev_read_u32_array(dev, "i2c,speeds", speeds, size); if (ret) return log_msg_ret("read", -E2BIG); speed = dev_read_u32_default(dev, "clock-frequency", 100000); acpigen_write_scope(ctx, path); ret = dw_i2c_gen_speed_config(dev, speed, &config); if (ret) return log_msg_ret("config", ret); dw_i2c_acpi_write_speed_config(ctx, &config); acpigen_pop_len(ctx); return 0; } struct acpi_ops dw_i2c_acpi_ops = { .fill_ssdt = dw_i2c_acpi_fill_ssdt, }; static const struct udevice_id designware_i2c_pci_ids[] = { { .compatible = "snps,designware-i2c-pci" }, { .compatible = "intel,apl-i2c", .data = INTEL_APL }, { } }; U_BOOT_DRIVER(i2c_designware_pci) = { .name = "i2c_designware_pci", .id = UCLASS_I2C, .of_match = designware_i2c_pci_ids, .bind = designware_i2c_pci_bind, .of_to_plat = designware_i2c_pci_of_to_plat, .probe = designware_i2c_pci_probe, .priv_auto = sizeof(struct dw_i2c), .remove = designware_i2c_remove, .flags = DM_FLAG_OS_PREPARE, .ops = &designware_i2c_ops, ACPI_OPS_PTR(&dw_i2c_acpi_ops) }; static struct pci_device_id designware_pci_supported[] = { /* Intel BayTrail has 7 I2C controller located on the PCI bus */ { PCI_VDEVICE(INTEL, 0x0f41) }, { PCI_VDEVICE(INTEL, 0x0f42) }, { PCI_VDEVICE(INTEL, 0x0f43) }, { PCI_VDEVICE(INTEL, 0x0f44) }, { PCI_VDEVICE(INTEL, 0x0f45) }, { PCI_VDEVICE(INTEL, 0x0f46) }, { PCI_VDEVICE(INTEL, 0x0f47) }, { PCI_VDEVICE(INTEL, 0x5aac), .driver_data = INTEL_APL }, { PCI_VDEVICE(INTEL, 0x5aae), .driver_data = INTEL_APL }, { PCI_VDEVICE(INTEL, 0x5ab0), .driver_data = INTEL_APL }, { PCI_VDEVICE(INTEL, 0x5ab2), .driver_data = INTEL_APL }, { PCI_VDEVICE(INTEL, 0x5ab4), .driver_data = INTEL_APL }, { PCI_VDEVICE(INTEL, 0x5ab6), .driver_data = INTEL_APL }, {}, }; U_BOOT_PCI_DEVICE(i2c_designware_pci, designware_pci_supported);