linux/drivers/mfd/intel_soc_pmic_crc.c
Hans de Goede 7beb2261bc mfd: intel_soc_pmic_crc: Add support for non ACPI instantiated i2c_client
On some x86 Bay Trail tablets which shipped with Android as factory OS,
the DSDT is so broken that the PMIC needs to be manually instantiated by
the special x86-android-tablets.ko "fixup" driver for cases like this.

Add an i2c_device_id table so that the driver can match on manually
instantiated i2c_client-s (which lack an ACPI fwnode to match on).

Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://lore.kernel.org/r/20241104150655.41402-3-hdegoede@redhat.com
Signed-off-by: Lee Jones <lee@kernel.org>
2024-11-12 12:15:42 +00:00

285 lines
7.4 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Device access for Crystal Cove PMIC
*
* Copyright (C) 2012-2014, 2022 Intel Corporation. All rights reserved.
*
* Author: Yang, Bin <bin.yang@intel.com>
* Author: Zhu, Lejun <lejun.zhu@linux.intel.com>
*/
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mfd/core.h>
#include <linux/mfd/intel_soc_pmic.h>
#include <linux/platform_data/x86/soc.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#define CRYSTAL_COVE_MAX_REGISTER 0xC6
#define CRYSTAL_COVE_REG_IRQLVL1 0x02
#define CRYSTAL_COVE_REG_MIRQLVL1 0x0E
#define CRYSTAL_COVE_IRQ_PWRSRC 0
#define CRYSTAL_COVE_IRQ_THRM 1
#define CRYSTAL_COVE_IRQ_BCU 2
#define CRYSTAL_COVE_IRQ_ADC 3
#define CRYSTAL_COVE_IRQ_CHGR 4
#define CRYSTAL_COVE_IRQ_GPIO 5
#define CRYSTAL_COVE_IRQ_VHDMIOCP 6
static const struct resource pwrsrc_resources[] = {
DEFINE_RES_IRQ_NAMED(CRYSTAL_COVE_IRQ_PWRSRC, "PWRSRC"),
};
static const struct resource thermal_resources[] = {
DEFINE_RES_IRQ_NAMED(CRYSTAL_COVE_IRQ_THRM, "THERMAL"),
};
static const struct resource bcu_resources[] = {
DEFINE_RES_IRQ_NAMED(CRYSTAL_COVE_IRQ_BCU, "BCU"),
};
static const struct resource adc_resources[] = {
DEFINE_RES_IRQ_NAMED(CRYSTAL_COVE_IRQ_ADC, "ADC"),
};
static const struct resource charger_resources[] = {
DEFINE_RES_IRQ_NAMED(CRYSTAL_COVE_IRQ_CHGR, "CHGR"),
};
static const struct resource gpio_resources[] = {
DEFINE_RES_IRQ_NAMED(CRYSTAL_COVE_IRQ_GPIO, "GPIO"),
};
static struct mfd_cell crystal_cove_byt_dev[] = {
{
.name = "crystal_cove_pwrsrc",
.num_resources = ARRAY_SIZE(pwrsrc_resources),
.resources = pwrsrc_resources,
},
{
.name = "crystal_cove_thermal",
.num_resources = ARRAY_SIZE(thermal_resources),
.resources = thermal_resources,
},
{
.name = "crystal_cove_bcu",
.num_resources = ARRAY_SIZE(bcu_resources),
.resources = bcu_resources,
},
{
.name = "crystal_cove_adc",
.num_resources = ARRAY_SIZE(adc_resources),
.resources = adc_resources,
},
{
.name = "crystal_cove_charger",
.num_resources = ARRAY_SIZE(charger_resources),
.resources = charger_resources,
},
{
.name = "crystal_cove_gpio",
.num_resources = ARRAY_SIZE(gpio_resources),
.resources = gpio_resources,
},
{
.name = "byt_crystal_cove_pmic",
},
{
.name = "crystal_cove_pwm",
},
};
static struct mfd_cell crystal_cove_cht_dev[] = {
{
.name = "crystal_cove_gpio",
.num_resources = ARRAY_SIZE(gpio_resources),
.resources = gpio_resources,
},
{
.name = "cht_crystal_cove_pmic",
},
{
.name = "crystal_cove_pwm",
},
};
static const struct regmap_config crystal_cove_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = CRYSTAL_COVE_MAX_REGISTER,
.cache_type = REGCACHE_NONE,
};
static const struct regmap_irq crystal_cove_irqs[] = {
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_PWRSRC, 0, BIT(CRYSTAL_COVE_IRQ_PWRSRC)),
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_THRM, 0, BIT(CRYSTAL_COVE_IRQ_THRM)),
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_BCU, 0, BIT(CRYSTAL_COVE_IRQ_BCU)),
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_ADC, 0, BIT(CRYSTAL_COVE_IRQ_ADC)),
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_CHGR, 0, BIT(CRYSTAL_COVE_IRQ_CHGR)),
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_GPIO, 0, BIT(CRYSTAL_COVE_IRQ_GPIO)),
REGMAP_IRQ_REG(CRYSTAL_COVE_IRQ_VHDMIOCP, 0, BIT(CRYSTAL_COVE_IRQ_VHDMIOCP)),
};
static const struct regmap_irq_chip crystal_cove_irq_chip = {
.name = "Crystal Cove",
.irqs = crystal_cove_irqs,
.num_irqs = ARRAY_SIZE(crystal_cove_irqs),
.num_regs = 1,
.status_base = CRYSTAL_COVE_REG_IRQLVL1,
.mask_base = CRYSTAL_COVE_REG_MIRQLVL1,
};
/* PWM consumed by the Intel GFX */
static struct pwm_lookup crc_pwm_lookup[] = {
PWM_LOOKUP_WITH_MODULE("crystal_cove_pwm", 0, "0000:00:02.0",
"pwm_pmic_backlight", 0, PWM_POLARITY_NORMAL,
"pwm-crc"),
};
struct crystal_cove_config {
unsigned long irq_flags;
struct mfd_cell *cell_dev;
int n_cell_devs;
const struct regmap_config *regmap_config;
const struct regmap_irq_chip *irq_chip;
};
static const struct crystal_cove_config crystal_cove_config_byt_crc = {
.irq_flags = IRQF_TRIGGER_RISING,
.cell_dev = crystal_cove_byt_dev,
.n_cell_devs = ARRAY_SIZE(crystal_cove_byt_dev),
.regmap_config = &crystal_cove_regmap_config,
.irq_chip = &crystal_cove_irq_chip,
};
static const struct crystal_cove_config crystal_cove_config_cht_crc = {
.irq_flags = IRQF_TRIGGER_RISING,
.cell_dev = crystal_cove_cht_dev,
.n_cell_devs = ARRAY_SIZE(crystal_cove_cht_dev),
.regmap_config = &crystal_cove_regmap_config,
.irq_chip = &crystal_cove_irq_chip,
};
static int crystal_cove_i2c_probe(struct i2c_client *i2c)
{
const struct crystal_cove_config *config;
struct device *dev = &i2c->dev;
struct intel_soc_pmic *pmic;
int ret;
if (soc_intel_is_byt())
config = &crystal_cove_config_byt_crc;
else
config = &crystal_cove_config_cht_crc;
pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL);
if (!pmic)
return -ENOMEM;
i2c_set_clientdata(i2c, pmic);
pmic->regmap = devm_regmap_init_i2c(i2c, config->regmap_config);
if (IS_ERR(pmic->regmap))
return PTR_ERR(pmic->regmap);
pmic->irq = i2c->irq;
ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq,
config->irq_flags | IRQF_ONESHOT,
0, config->irq_chip, &pmic->irq_chip_data);
if (ret)
return ret;
ret = enable_irq_wake(pmic->irq);
if (ret)
dev_warn(dev, "Can't enable IRQ as wake source: %d\n", ret);
/* Add lookup table for crc-pwm */
pwm_add_table(crc_pwm_lookup, ARRAY_SIZE(crc_pwm_lookup));
/* To distuingish this domain from the GPIO/charger's irqchip domains */
irq_domain_update_bus_token(regmap_irq_get_domain(pmic->irq_chip_data),
DOMAIN_BUS_NEXUS);
ret = mfd_add_devices(dev, PLATFORM_DEVID_NONE, config->cell_dev,
config->n_cell_devs, NULL, 0,
regmap_irq_get_domain(pmic->irq_chip_data));
if (ret)
pwm_remove_table(crc_pwm_lookup, ARRAY_SIZE(crc_pwm_lookup));
return ret;
}
static void crystal_cove_i2c_remove(struct i2c_client *i2c)
{
/* remove crc-pwm lookup table */
pwm_remove_table(crc_pwm_lookup, ARRAY_SIZE(crc_pwm_lookup));
mfd_remove_devices(&i2c->dev);
}
static void crystal_cove_shutdown(struct i2c_client *i2c)
{
struct intel_soc_pmic *pmic = i2c_get_clientdata(i2c);
disable_irq(pmic->irq);
return;
}
static int crystal_cove_suspend(struct device *dev)
{
struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
disable_irq(pmic->irq);
return 0;
}
static int crystal_cove_resume(struct device *dev)
{
struct intel_soc_pmic *pmic = dev_get_drvdata(dev);
enable_irq(pmic->irq);
return 0;
}
static DEFINE_SIMPLE_DEV_PM_OPS(crystal_cove_pm_ops, crystal_cove_suspend, crystal_cove_resume);
static const struct acpi_device_id crystal_cove_acpi_match[] = {
{ "INT33FD" },
{ },
};
MODULE_DEVICE_TABLE(acpi, crystal_cove_acpi_match);
static const struct i2c_device_id crystal_cove_i2c_match[] = {
{ "intel_soc_pmic_crc" },
{ }
};
MODULE_DEVICE_TABLE(i2c, crystal_cove_i2c_match);
static struct i2c_driver crystal_cove_i2c_driver = {
.driver = {
.name = "intel_soc_pmic_crc",
.pm = pm_sleep_ptr(&crystal_cove_pm_ops),
.acpi_match_table = crystal_cove_acpi_match,
},
.id_table = crystal_cove_i2c_match,
.probe = crystal_cove_i2c_probe,
.remove = crystal_cove_i2c_remove,
.shutdown = crystal_cove_shutdown,
};
module_i2c_driver(crystal_cove_i2c_driver);
MODULE_DESCRIPTION("I2C driver for Intel SoC PMIC");
MODULE_AUTHOR("Yang, Bin <bin.yang@intel.com>");
MODULE_AUTHOR("Zhu, Lejun <lejun.zhu@linux.intel.com>");