Commit | Line | Data |
---|---|---|
26c7e05a | 1 | // SPDX-License-Identifier: GPL-2.0 |
de85d79f HG |
2 | /* |
3 | * MFD core driver for Intel Cherrytrail Whiskey Cove PMIC | |
4 | * | |
5 | * Copyright (C) 2017 Hans de Goede <hdegoede@redhat.com> | |
6 | * | |
7 | * Based on various non upstream patches to support the CHT Whiskey Cove PMIC: | |
8 | * Copyright (C) 2013-2015 Intel Corporation. All rights reserved. | |
de85d79f HG |
9 | */ |
10 | ||
11 | #include <linux/acpi.h> | |
12 | #include <linux/delay.h> | |
13 | #include <linux/err.h> | |
14 | #include <linux/i2c.h> | |
15 | #include <linux/interrupt.h> | |
16 | #include <linux/kernel.h> | |
17 | #include <linux/mfd/core.h> | |
18 | #include <linux/mfd/intel_soc_pmic.h> | |
19 | #include <linux/regmap.h> | |
20 | ||
21 | /* PMIC device registers */ | |
22 | #define REG_OFFSET_MASK GENMASK(7, 0) | |
23 | #define REG_ADDR_MASK GENMASK(15, 8) | |
24 | #define REG_ADDR_SHIFT 8 | |
25 | ||
26 | #define CHT_WC_IRQLVL1 0x6e02 | |
27 | #define CHT_WC_IRQLVL1_MASK 0x6e0e | |
28 | ||
29 | /* Whiskey Cove PMIC share same ACPI ID between different platforms */ | |
30 | #define CHT_WC_HRV 3 | |
31 | ||
32 | /* Level 1 IRQs (level 2 IRQs are handled in the child device drivers) */ | |
33 | enum { | |
34 | CHT_WC_PWRSRC_IRQ = 0, | |
35 | CHT_WC_THRM_IRQ, | |
36 | CHT_WC_BCU_IRQ, | |
37 | CHT_WC_ADC_IRQ, | |
38 | CHT_WC_EXT_CHGR_IRQ, | |
39 | CHT_WC_GPIO_IRQ, | |
40 | /* There is no irq 6 */ | |
41 | CHT_WC_CRIT_IRQ = 7, | |
42 | }; | |
43 | ||
44 | static struct resource cht_wc_pwrsrc_resources[] = { | |
45 | DEFINE_RES_IRQ(CHT_WC_PWRSRC_IRQ), | |
46 | }; | |
47 | ||
48 | static struct resource cht_wc_ext_charger_resources[] = { | |
49 | DEFINE_RES_IRQ(CHT_WC_EXT_CHGR_IRQ), | |
50 | }; | |
51 | ||
52 | static struct mfd_cell cht_wc_dev[] = { | |
53 | { | |
54 | .name = "cht_wcove_pwrsrc", | |
55 | .num_resources = ARRAY_SIZE(cht_wc_pwrsrc_resources), | |
56 | .resources = cht_wc_pwrsrc_resources, | |
57 | }, { | |
58 | .name = "cht_wcove_ext_chgr", | |
59 | .num_resources = ARRAY_SIZE(cht_wc_ext_charger_resources), | |
60 | .resources = cht_wc_ext_charger_resources, | |
61 | }, | |
62 | { .name = "cht_wcove_region", }, | |
63 | }; | |
64 | ||
65 | /* | |
66 | * The CHT Whiskey Cove covers multiple I2C addresses, with a 1 Byte | |
67 | * register address space per I2C address, so we use 16 bit register | |
68 | * addresses where the high 8 bits contain the I2C client address. | |
69 | */ | |
70 | static int cht_wc_byte_reg_read(void *context, unsigned int reg, | |
71 | unsigned int *val) | |
72 | { | |
73 | struct i2c_client *client = context; | |
74 | int ret, orig_addr = client->addr; | |
75 | ||
76 | if (!(reg & REG_ADDR_MASK)) { | |
77 | dev_err(&client->dev, "Error I2C address not specified\n"); | |
78 | return -EINVAL; | |
79 | } | |
80 | ||
81 | client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT; | |
82 | ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK); | |
83 | client->addr = orig_addr; | |
84 | ||
85 | if (ret < 0) | |
86 | return ret; | |
87 | ||
88 | *val = ret; | |
89 | return 0; | |
90 | } | |
91 | ||
92 | static int cht_wc_byte_reg_write(void *context, unsigned int reg, | |
93 | unsigned int val) | |
94 | { | |
95 | struct i2c_client *client = context; | |
96 | int ret, orig_addr = client->addr; | |
97 | ||
98 | if (!(reg & REG_ADDR_MASK)) { | |
99 | dev_err(&client->dev, "Error I2C address not specified\n"); | |
100 | return -EINVAL; | |
101 | } | |
102 | ||
103 | client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT; | |
104 | ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val); | |
105 | client->addr = orig_addr; | |
106 | ||
107 | return ret; | |
108 | } | |
109 | ||
110 | static const struct regmap_config cht_wc_regmap_cfg = { | |
111 | .reg_bits = 16, | |
112 | .val_bits = 8, | |
113 | .reg_write = cht_wc_byte_reg_write, | |
114 | .reg_read = cht_wc_byte_reg_read, | |
115 | }; | |
116 | ||
117 | static const struct regmap_irq cht_wc_regmap_irqs[] = { | |
118 | REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)), | |
119 | REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)), | |
120 | REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)), | |
121 | REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)), | |
122 | REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)), | |
123 | REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)), | |
124 | REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)), | |
125 | }; | |
126 | ||
127 | static const struct regmap_irq_chip cht_wc_regmap_irq_chip = { | |
128 | .name = "cht_wc_irq_chip", | |
129 | .status_base = CHT_WC_IRQLVL1, | |
130 | .mask_base = CHT_WC_IRQLVL1_MASK, | |
131 | .irqs = cht_wc_regmap_irqs, | |
132 | .num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs), | |
133 | .num_regs = 1, | |
134 | }; | |
135 | ||
136 | static int cht_wc_probe(struct i2c_client *client) | |
137 | { | |
138 | struct device *dev = &client->dev; | |
139 | struct intel_soc_pmic *pmic; | |
140 | acpi_status status; | |
141 | unsigned long long hrv; | |
142 | int ret; | |
143 | ||
144 | status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv); | |
145 | if (ACPI_FAILURE(status)) { | |
146 | dev_err(dev, "Failed to get PMIC hardware revision\n"); | |
147 | return -ENODEV; | |
148 | } | |
149 | if (hrv != CHT_WC_HRV) { | |
150 | dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv); | |
151 | return -ENODEV; | |
152 | } | |
153 | if (client->irq < 0) { | |
154 | dev_err(dev, "Invalid IRQ\n"); | |
155 | return -EINVAL; | |
156 | } | |
157 | ||
158 | pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); | |
159 | if (!pmic) | |
160 | return -ENOMEM; | |
161 | ||
162 | pmic->irq = client->irq; | |
163 | pmic->dev = dev; | |
164 | i2c_set_clientdata(client, pmic); | |
165 | ||
166 | pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg); | |
167 | if (IS_ERR(pmic->regmap)) | |
168 | return PTR_ERR(pmic->regmap); | |
169 | ||
170 | ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq, | |
171 | IRQF_ONESHOT | IRQF_SHARED, 0, | |
172 | &cht_wc_regmap_irq_chip, | |
173 | &pmic->irq_chip_data); | |
174 | if (ret) | |
175 | return ret; | |
176 | ||
177 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
178 | cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0, | |
179 | regmap_irq_get_domain(pmic->irq_chip_data)); | |
180 | } | |
181 | ||
182 | static void cht_wc_shutdown(struct i2c_client *client) | |
183 | { | |
184 | struct intel_soc_pmic *pmic = i2c_get_clientdata(client); | |
185 | ||
186 | disable_irq(pmic->irq); | |
187 | } | |
188 | ||
189 | static int __maybe_unused cht_wc_suspend(struct device *dev) | |
190 | { | |
191 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev); | |
192 | ||
193 | disable_irq(pmic->irq); | |
194 | ||
195 | return 0; | |
196 | } | |
197 | ||
198 | static int __maybe_unused cht_wc_resume(struct device *dev) | |
199 | { | |
200 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev); | |
201 | ||
202 | enable_irq(pmic->irq); | |
203 | ||
204 | return 0; | |
205 | } | |
206 | static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume); | |
207 | ||
208 | static const struct i2c_device_id cht_wc_i2c_id[] = { | |
209 | { } | |
210 | }; | |
211 | ||
212 | static const struct acpi_device_id cht_wc_acpi_ids[] = { | |
213 | { "INT34D3", }, | |
214 | { } | |
215 | }; | |
216 | ||
217 | static struct i2c_driver cht_wc_driver = { | |
218 | .driver = { | |
219 | .name = "CHT Whiskey Cove PMIC", | |
220 | .pm = &cht_wc_pm_ops, | |
221 | .acpi_match_table = cht_wc_acpi_ids, | |
222 | }, | |
223 | .probe_new = cht_wc_probe, | |
224 | .shutdown = cht_wc_shutdown, | |
225 | .id_table = cht_wc_i2c_id, | |
226 | }; | |
227 | builtin_i2c_driver(cht_wc_driver); |