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