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", }, | |
236c765d | 63 | { .name = "cht_wcove_leds", }, |
de85d79f HG |
64 | }; |
65 | ||
66 | /* | |
67 | * The CHT Whiskey Cove covers multiple I2C addresses, with a 1 Byte | |
68 | * register address space per I2C address, so we use 16 bit register | |
69 | * addresses where the high 8 bits contain the I2C client address. | |
70 | */ | |
71 | static int cht_wc_byte_reg_read(void *context, unsigned int reg, | |
72 | unsigned int *val) | |
73 | { | |
74 | struct i2c_client *client = context; | |
75 | int ret, orig_addr = client->addr; | |
76 | ||
77 | if (!(reg & REG_ADDR_MASK)) { | |
78 | dev_err(&client->dev, "Error I2C address not specified\n"); | |
79 | return -EINVAL; | |
80 | } | |
81 | ||
82 | client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT; | |
83 | ret = i2c_smbus_read_byte_data(client, reg & REG_OFFSET_MASK); | |
84 | client->addr = orig_addr; | |
85 | ||
86 | if (ret < 0) | |
87 | return ret; | |
88 | ||
89 | *val = ret; | |
90 | return 0; | |
91 | } | |
92 | ||
93 | static int cht_wc_byte_reg_write(void *context, unsigned int reg, | |
94 | unsigned int val) | |
95 | { | |
96 | struct i2c_client *client = context; | |
97 | int ret, orig_addr = client->addr; | |
98 | ||
99 | if (!(reg & REG_ADDR_MASK)) { | |
100 | dev_err(&client->dev, "Error I2C address not specified\n"); | |
101 | return -EINVAL; | |
102 | } | |
103 | ||
104 | client->addr = (reg & REG_ADDR_MASK) >> REG_ADDR_SHIFT; | |
105 | ret = i2c_smbus_write_byte_data(client, reg & REG_OFFSET_MASK, val); | |
106 | client->addr = orig_addr; | |
107 | ||
108 | return ret; | |
109 | } | |
110 | ||
111 | static const struct regmap_config cht_wc_regmap_cfg = { | |
112 | .reg_bits = 16, | |
113 | .val_bits = 8, | |
114 | .reg_write = cht_wc_byte_reg_write, | |
115 | .reg_read = cht_wc_byte_reg_read, | |
116 | }; | |
117 | ||
118 | static const struct regmap_irq cht_wc_regmap_irqs[] = { | |
119 | REGMAP_IRQ_REG(CHT_WC_PWRSRC_IRQ, 0, BIT(CHT_WC_PWRSRC_IRQ)), | |
120 | REGMAP_IRQ_REG(CHT_WC_THRM_IRQ, 0, BIT(CHT_WC_THRM_IRQ)), | |
121 | REGMAP_IRQ_REG(CHT_WC_BCU_IRQ, 0, BIT(CHT_WC_BCU_IRQ)), | |
122 | REGMAP_IRQ_REG(CHT_WC_ADC_IRQ, 0, BIT(CHT_WC_ADC_IRQ)), | |
123 | REGMAP_IRQ_REG(CHT_WC_EXT_CHGR_IRQ, 0, BIT(CHT_WC_EXT_CHGR_IRQ)), | |
124 | REGMAP_IRQ_REG(CHT_WC_GPIO_IRQ, 0, BIT(CHT_WC_GPIO_IRQ)), | |
125 | REGMAP_IRQ_REG(CHT_WC_CRIT_IRQ, 0, BIT(CHT_WC_CRIT_IRQ)), | |
126 | }; | |
127 | ||
128 | static const struct regmap_irq_chip cht_wc_regmap_irq_chip = { | |
129 | .name = "cht_wc_irq_chip", | |
130 | .status_base = CHT_WC_IRQLVL1, | |
131 | .mask_base = CHT_WC_IRQLVL1_MASK, | |
132 | .irqs = cht_wc_regmap_irqs, | |
133 | .num_irqs = ARRAY_SIZE(cht_wc_regmap_irqs), | |
134 | .num_regs = 1, | |
135 | }; | |
136 | ||
137 | static int cht_wc_probe(struct i2c_client *client) | |
138 | { | |
139 | struct device *dev = &client->dev; | |
140 | struct intel_soc_pmic *pmic; | |
141 | acpi_status status; | |
142 | unsigned long long hrv; | |
143 | int ret; | |
144 | ||
145 | status = acpi_evaluate_integer(ACPI_HANDLE(dev), "_HRV", NULL, &hrv); | |
146 | if (ACPI_FAILURE(status)) { | |
147 | dev_err(dev, "Failed to get PMIC hardware revision\n"); | |
148 | return -ENODEV; | |
149 | } | |
150 | if (hrv != CHT_WC_HRV) { | |
151 | dev_err(dev, "Invalid PMIC hardware revision: %llu\n", hrv); | |
152 | return -ENODEV; | |
153 | } | |
154 | if (client->irq < 0) { | |
155 | dev_err(dev, "Invalid IRQ\n"); | |
156 | return -EINVAL; | |
157 | } | |
158 | ||
159 | pmic = devm_kzalloc(dev, sizeof(*pmic), GFP_KERNEL); | |
160 | if (!pmic) | |
161 | return -ENOMEM; | |
162 | ||
163 | pmic->irq = client->irq; | |
164 | pmic->dev = dev; | |
165 | i2c_set_clientdata(client, pmic); | |
166 | ||
167 | pmic->regmap = devm_regmap_init(dev, NULL, client, &cht_wc_regmap_cfg); | |
168 | if (IS_ERR(pmic->regmap)) | |
169 | return PTR_ERR(pmic->regmap); | |
170 | ||
171 | ret = devm_regmap_add_irq_chip(dev, pmic->regmap, pmic->irq, | |
172 | IRQF_ONESHOT | IRQF_SHARED, 0, | |
173 | &cht_wc_regmap_irq_chip, | |
174 | &pmic->irq_chip_data); | |
175 | if (ret) | |
176 | return ret; | |
177 | ||
178 | return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, | |
179 | cht_wc_dev, ARRAY_SIZE(cht_wc_dev), NULL, 0, | |
180 | regmap_irq_get_domain(pmic->irq_chip_data)); | |
181 | } | |
182 | ||
183 | static void cht_wc_shutdown(struct i2c_client *client) | |
184 | { | |
185 | struct intel_soc_pmic *pmic = i2c_get_clientdata(client); | |
186 | ||
187 | disable_irq(pmic->irq); | |
188 | } | |
189 | ||
190 | static int __maybe_unused cht_wc_suspend(struct device *dev) | |
191 | { | |
192 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev); | |
193 | ||
194 | disable_irq(pmic->irq); | |
195 | ||
196 | return 0; | |
197 | } | |
198 | ||
199 | static int __maybe_unused cht_wc_resume(struct device *dev) | |
200 | { | |
201 | struct intel_soc_pmic *pmic = dev_get_drvdata(dev); | |
202 | ||
203 | enable_irq(pmic->irq); | |
204 | ||
205 | return 0; | |
206 | } | |
207 | static SIMPLE_DEV_PM_OPS(cht_wc_pm_ops, cht_wc_suspend, cht_wc_resume); | |
208 | ||
209 | static const struct i2c_device_id cht_wc_i2c_id[] = { | |
210 | { } | |
211 | }; | |
212 | ||
213 | static const struct acpi_device_id cht_wc_acpi_ids[] = { | |
214 | { "INT34D3", }, | |
215 | { } | |
216 | }; | |
217 | ||
218 | static struct i2c_driver cht_wc_driver = { | |
219 | .driver = { | |
220 | .name = "CHT Whiskey Cove PMIC", | |
221 | .pm = &cht_wc_pm_ops, | |
222 | .acpi_match_table = cht_wc_acpi_ids, | |
223 | }, | |
224 | .probe_new = cht_wc_probe, | |
225 | .shutdown = cht_wc_shutdown, | |
226 | .id_table = cht_wc_i2c_id, | |
227 | }; | |
228 | builtin_i2c_driver(cht_wc_driver); |