| // 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("crystal_cove_pwm", 0, "0000:00:02.0", "pwm_pmic_backlight", 0, PWM_POLARITY_NORMAL), |
| }; |
| |
| 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 struct i2c_driver crystal_cove_i2c_driver = { |
| .driver = { |
| .name = "crystal_cove_i2c", |
| .pm = pm_sleep_ptr(&crystal_cove_pm_ops), |
| .acpi_match_table = crystal_cove_acpi_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>"); |