mfd: Add Cherry Trail Whiskey Cove PMIC driver
Add mfd driver for Intel CHT Whiskey Cove PMIC, based on various non upstreamed CHT Whiskey Cove PMIC patches. This is a somewhat minimal version which adds irqchip support and cells for: ACPI PMIC opregion support, the i2c-controller driving the external charger irc and the pwrsrc/extcon block. Further cells can be added in the future if/when drivers are upstreamed for them. [The above patch caused a build error on some archetectures] From: Arnd Bergmann <arnd@arndb.de> I ran into a build error on ARM with a platform that has a non-standard clk implementation: drivers/clk/clk.o: In function `clk_disable': clk.c:(.text.clk_disable+0x0): multiple definition of `clk_disable' arch/arm/mach-omap1/clock.o:clock.c:(.text.clk_disable+0x0): first defined here drivers/clk/clk.o: In function `clk_enable': clk.c:(.text.clk_enable+0x0): multiple definition of `clk_enable' arch/arm/mach-omap1/clock.o:clock.c:(.text.clk_enable+0x0): first defined here The problem is a device driver that uses 'select COMMON_CLK', which is generally a bad idea: selecting a subsystem should only be done from a platform, otherwise we run into circular dependencies. The same driver also selects 'GPIOLIB' and 'I2C', which has a similar effect. This turns all three into 'depends on', as it should be. Finally, we can limit the build to x86, unless we are compile testing. First patch: Signed-off-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com> Signed-off-by: Lee Jones <lee.jones@linaro.org> Fix for first patch (squashed): Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Lee Jones <lee.jones@linaro.org>
This commit is contained in:
		
							parent
							
								
									b6678050a1
								
							
						
					
					
						commit
						de85d79f4a
					
				| @ -475,6 +475,22 @@ config INTEL_SOC_PMIC_BXTWC | |||||||
| 	  thermal, charger and related power management functions | 	  thermal, charger and related power management functions | ||||||
| 	  on these systems. | 	  on these systems. | ||||||
| 
 | 
 | ||||||
|  | config INTEL_SOC_PMIC_CHTWC | ||||||
|  | 	tristate "Support for Intel Cherry Trail Whiskey Cove PMIC" | ||||||
|  | 	depends on ACPI && HAS_IOMEM && I2C=y && COMMON_CLK | ||||||
|  | 	depends on X86 || COMPILE_TEST | ||||||
|  | 	select MFD_CORE | ||||||
|  | 	select REGMAP_I2C | ||||||
|  | 	select REGMAP_IRQ | ||||||
|  | 	select I2C_DESIGNWARE_PLATFORM | ||||||
|  | 	help | ||||||
|  | 	  Select this option to enable support for the Intel Cherry Trail | ||||||
|  | 	  Whiskey Cove PMIC found on some Intel Cherry Trail systems. | ||||||
|  | 
 | ||||||
|  | 	  This option is a bool as it provides an ACPI OpRegion which must be | ||||||
|  | 	  available before any devices using it are probed. This option also | ||||||
|  | 	  causes the designware-i2c driver to be builtin for the same reason. | ||||||
|  | 
 | ||||||
| config MFD_INTEL_LPSS | config MFD_INTEL_LPSS | ||||||
| 	tristate | 	tristate | ||||||
| 	select COMMON_CLK | 	select COMMON_CLK | ||||||
|  | |||||||
| @ -214,6 +214,7 @@ obj-$(CONFIG_MFD_SKY81452)	+= sky81452.o | |||||||
| intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o | intel-soc-pmic-objs		:= intel_soc_pmic_core.o intel_soc_pmic_crc.o | ||||||
| obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o | obj-$(CONFIG_INTEL_SOC_PMIC)	+= intel-soc-pmic.o | ||||||
| obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC)	+= intel_soc_pmic_bxtwc.o | obj-$(CONFIG_INTEL_SOC_PMIC_BXTWC)	+= intel_soc_pmic_bxtwc.o | ||||||
|  | obj-$(CONFIG_INTEL_SOC_PMIC_CHTWC)	+= intel_soc_pmic_chtwc.o | ||||||
| obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o | obj-$(CONFIG_MFD_MT6397)	+= mt6397-core.o | ||||||
| 
 | 
 | ||||||
| obj-$(CONFIG_MFD_ALTERA_A10SR)	+= altera-a10sr.o | obj-$(CONFIG_MFD_ALTERA_A10SR)	+= altera-a10sr.o | ||||||
|  | |||||||
							
								
								
									
										230
									
								
								drivers/mfd/intel_soc_pmic_chtwc.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								drivers/mfd/intel_soc_pmic_chtwc.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,230 @@ | |||||||
|  | /*
 | ||||||
|  |  * MFD core driver for Intel Cherrytrail Whiskey Cove PMIC | ||||||
|  |  * | ||||||
|  |  * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> | ||||||
|  |  * | ||||||
|  |  * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: | ||||||
|  |  * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify | ||||||
|  |  * it under the terms of the GNU General Public License version 2 as | ||||||
|  |  * published by the Free Software Foundation. | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | #include <linux/acpi.h> | ||||||
|  | #include <linux/delay.h> | ||||||
|  | #include <linux/err.h> | ||||||
|  | #include <linux/i2c.h> | ||||||
|  | #include <linux/interrupt.h> | ||||||
|  | #include <linux/kernel.h> | ||||||
|  | #include <linux/mfd/core.h> | ||||||
|  | #include <linux/mfd/intel_soc_pmic.h> | ||||||
|  | #include <linux/regmap.h> | ||||||
|  | 
 | ||||||
|  | /* PMIC device registers */ | ||||||
|  | #define REG_OFFSET_MASK		GENMASK(7, 0) | ||||||
|  | #define REG_ADDR_MASK		GENMASK(15, 8) | ||||||
|  | #define REG_ADDR_SHIFT		8 | ||||||
|  | 
 | ||||||
|  | #define CHT_WC_IRQLVL1		0x6e02 | ||||||
|  | #define CHT_WC_IRQLVL1_MASK	0x6e0e | ||||||
|  | 
 | ||||||
|  | /* Whiskey Cove PMIC share same ACPI ID between different platforms */ | ||||||
|  | #define CHT_WC_HRV		3 | ||||||
|  | 
 | ||||||
|  | /* Level 1 IRQs (level 2 IRQs are handled in the child device drivers) */ | ||||||
|  | enum { | ||||||
|  | 	CHT_WC_PWRSRC_IRQ = 0, | ||||||
|  | 	CHT_WC_THRM_IRQ, | ||||||
|  | 	CHT_WC_BCU_IRQ, | ||||||
|  | 	CHT_WC_ADC_IRQ, | ||||||
|  | 	CHT_WC_EXT_CHGR_IRQ, | ||||||
|  | 	CHT_WC_GPIO_IRQ, | ||||||
|  | 	/* There is no irq 6 */ | ||||||
|  | 	CHT_WC_CRIT_IRQ = 7, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct resource cht_wc_pwrsrc_resources[] = { | ||||||
|  | 	DEFINE_RES_IRQ(CHT_WC_PWRSRC_IRQ), | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct resource cht_wc_ext_charger_resources[] = { | ||||||
|  | 	DEFINE_RES_IRQ(CHT_WC_EXT_CHGR_IRQ), | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct mfd_cell cht_wc_dev[] = { | ||||||
|  | 	{ | ||||||
|  | 		.name = "cht_wcove_pwrsrc", | ||||||
|  | 		.num_resources = ARRAY_SIZE(cht_wc_pwrsrc_resources), | ||||||
|  | 		.resources = cht_wc_pwrsrc_resources, | ||||||
|  | 	}, { | ||||||
|  | 		.name = "cht_wcove_ext_chgr", | ||||||
|  | 		.num_resources = ARRAY_SIZE(cht_wc_ext_charger_resources), | ||||||
|  | 		.resources = cht_wc_ext_charger_resources, | ||||||
|  | 	}, | ||||||
|  | 	{	.name = "cht_wcove_region", }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * The CHT Whiskey Cove covers multiple I2C addresses, with a 1 Byte | ||||||
|  |  * register address space per I2C address, so we use 16 bit register | ||||||
|  |  * addresses where the high 8 bits contain the I2C client address. | ||||||
|  |  */ | ||||||
|  | static int cht_wc_byte_reg_read(void *context, unsigned int reg, | ||||||
|  | 				unsigned int *val) | ||||||
|  | { | ||||||
|  | 	struct i2c_client *client = context; | ||||||
|  | 	int ret, orig_addr = client->addr; | ||||||
|  | 
 | ||||||
|  | 	if (!(reg & REG_ADDR_MASK)) { | ||||||
|  | 		dev_err(&client->dev, "Error I2C address not specified\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT; | ||||||
|  | 	ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK); | ||||||
|  | 	client->addr = orig_addr; | ||||||
|  | 
 | ||||||
|  | 	if (ret < 0) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	*val = ret; | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int cht_wc_byte_reg_write(void *context, unsigned int reg, | ||||||
|  | 				 unsigned int val) | ||||||
|  | { | ||||||
|  | 	struct i2c_client *client = context; | ||||||
|  | 	int ret, orig_addr = client->addr; | ||||||
|  | 
 | ||||||
|  | 	if (!(reg & REG_ADDR_MASK)) { | ||||||
|  | 		dev_err(&client->dev, "Error I2C address not specified\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT; | ||||||
|  | 	ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val); | ||||||
|  | 	client->addr = orig_addr; | ||||||
|  | 
 | ||||||
|  | 	return ret; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static const struct regmap_config cht_wc_regmap_cfg = { | ||||||
|  | 	.reg_bits = 16, | ||||||
|  | 	.val_bits = 8, | ||||||
|  | 	.reg_write = cht_wc_byte_reg_write, | ||||||
|  | 	.reg_read = cht_wc_byte_reg_read, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct regmap_irq cht_wc_regmap_irqs[] = { | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)), | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)), | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)), | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)), | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)), | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)), | ||||||
|  | 	REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)), | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct regmap_irq_chip cht_wc_regmap_irq_chip = { | ||||||
|  | 	.name = "cht_wc_irq_chip", | ||||||
|  | 	.status_base = CHT_WC_IRQLVL1, | ||||||
|  | 	.mask_base = CHT_WC_IRQLVL1_MASK, | ||||||
|  | 	.irqs = cht_wc_regmap_irqs, | ||||||
|  | 	.num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs), | ||||||
|  | 	.num_regs = 1, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static int cht_wc_probe(struct i2c_client *client) | ||||||
|  | { | ||||||
|  | 	struct device *dev = &client->dev; | ||||||
|  | 	struct intel_soc_pmic *pmic; | ||||||
|  | 	acpi_status status; | ||||||
|  | 	unsigned long long hrv; | ||||||
|  | 	int ret; | ||||||
|  | 
 | ||||||
|  | 	status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv); | ||||||
|  | 	if (ACPI_FAILURE(status)) { | ||||||
|  | 		dev_err(dev, "Failed to get PMIC hardware revision\n"); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 	if (hrv != CHT_WC_HRV) { | ||||||
|  | 		dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv); | ||||||
|  | 		return -ENODEV; | ||||||
|  | 	} | ||||||
|  | 	if (client->irq < 0) { | ||||||
|  | 		dev_err(dev, "Invalid IRQ\n"); | ||||||
|  | 		return -EINVAL; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); | ||||||
|  | 	if (!pmic) | ||||||
|  | 		return -ENOMEM; | ||||||
|  | 
 | ||||||
|  | 	pmic->irq = client->irq; | ||||||
|  | 	pmic->dev = dev; | ||||||
|  | 	i2c_set_clientdata(client, pmic); | ||||||
|  | 
 | ||||||
|  | 	pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg); | ||||||
|  | 	if (IS_ERR(pmic->regmap)) | ||||||
|  | 		return PTR_ERR(pmic->regmap); | ||||||
|  | 
 | ||||||
|  | 	ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq, | ||||||
|  | 				       IRQF_ONESHOT | IRQF_SHARED, 0, | ||||||
|  | 				       &cht_wc_regmap_irq_chip, | ||||||
|  | 				       &pmic->irq_chip_data); | ||||||
|  | 	if (ret) | ||||||
|  | 		return ret; | ||||||
|  | 
 | ||||||
|  | 	return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | ||||||
|  | 				cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0, | ||||||
|  | 				regmap_irq_get_domain(pmic->irq_chip_data)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void cht_wc_shutdown(struct i2c_client *client) | ||||||
|  | { | ||||||
|  | 	struct intel_soc_pmic *pmic = i2c_get_clientdata(client); | ||||||
|  | 
 | ||||||
|  | 	disable_irq(pmic->irq); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int __maybe_unused cht_wc_suspend(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct intel_soc_pmic *pmic = dev_get_drvdata(dev); | ||||||
|  | 
 | ||||||
|  | 	disable_irq(pmic->irq); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int __maybe_unused cht_wc_resume(struct device *dev) | ||||||
|  | { | ||||||
|  | 	struct intel_soc_pmic *pmic = dev_get_drvdata(dev); | ||||||
|  | 
 | ||||||
|  | 	enable_irq(pmic->irq); | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume); | ||||||
|  | 
 | ||||||
|  | static const struct i2c_device_id cht_wc_i2c_id[] = { | ||||||
|  | 	{ } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const struct acpi_device_id cht_wc_acpi_ids[] = { | ||||||
|  | 	{ "INT34D3", }, | ||||||
|  | 	{ } | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct i2c_driver cht_wc_driver = { | ||||||
|  | 	.driver	= { | ||||||
|  | 		.name	= "CHT Whiskey Cove PMIC", | ||||||
|  | 		.pm     = &cht_wc_pm_ops, | ||||||
|  | 		.acpi_match_table = cht_wc_acpi_ids, | ||||||
|  | 	}, | ||||||
|  | 	.probe_new = cht_wc_probe, | ||||||
|  | 	.shutdown = cht_wc_shutdown, | ||||||
|  | 	.id_table = cht_wc_i2c_id, | ||||||
|  | }; | ||||||
|  | builtin_i2c_driver(cht_wc_driver); | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user