1 // SPDX-License-Identifier: GPL-2.0-only
3 * Copyright (C) 2017 Spreadtrum Communications Inc.
6 #include <linux/interrupt.h>
7 #include <linux/kernel.h>
8 #include <linux/module.h>
9 #include <linux/mfd/core.h>
10 #include <linux/mfd/sc27xx-pmic.h>
11 #include <linux/of_device.h>
12 #include <linux/of_platform.h>
13 #include <linux/regmap.h>
14 #include <linux/spi/spi.h>
15 #include <uapi/linux/usb/charger.h>
17 #define SPRD_PMIC_INT_MASK_STATUS 0x0
18 #define SPRD_PMIC_INT_RAW_STATUS 0x4
19 #define SPRD_PMIC_INT_EN 0x8
21 #define SPRD_SC2731_IRQ_BASE 0x140
22 #define SPRD_SC2731_IRQ_NUMS 16
23 #define SPRD_SC2731_CHG_DET 0xedc
25 /* PMIC charger detection definition */
26 #define SPRD_PMIC_CHG_DET_DELAY_US 200000
27 #define SPRD_PMIC_CHG_DET_TIMEOUT 2000000
28 #define SPRD_PMIC_CHG_DET_DONE BIT(11)
29 #define SPRD_PMIC_SDP_TYPE BIT(7)
30 #define SPRD_PMIC_DCP_TYPE BIT(6)
31 #define SPRD_PMIC_CDP_TYPE BIT(5)
32 #define SPRD_PMIC_CHG_TYPE_MASK GENMASK(7, 5)
35 struct regmap
*regmap
;
37 struct regmap_irq
*irqs
;
38 struct regmap_irq_chip irq_chip
;
39 struct regmap_irq_chip_data
*irq_data
;
40 const struct sprd_pmic_data
*pdata
;
44 struct sprd_pmic_data
{
51 * Since different PMICs of SC27xx series can have different interrupt
52 * base address and irq number, we should save irq number and irq base
53 * in the device data structure.
55 static const struct sprd_pmic_data sc2731_data
= {
56 .irq_base
= SPRD_SC2731_IRQ_BASE
,
57 .num_irqs
= SPRD_SC2731_IRQ_NUMS
,
58 .charger_det
= SPRD_SC2731_CHG_DET
,
61 enum usb_charger_type
sprd_pmic_detect_charger_type(struct device
*dev
)
63 struct spi_device
*spi
= to_spi_device(dev
);
64 struct sprd_pmic
*ddata
= spi_get_drvdata(spi
);
65 const struct sprd_pmic_data
*pdata
= ddata
->pdata
;
66 enum usb_charger_type type
;
70 ret
= regmap_read_poll_timeout(ddata
->regmap
, pdata
->charger_det
, val
,
71 (val
& SPRD_PMIC_CHG_DET_DONE
),
72 SPRD_PMIC_CHG_DET_DELAY_US
,
73 SPRD_PMIC_CHG_DET_TIMEOUT
);
75 dev_err(&spi
->dev
, "failed to detect charger type\n");
79 switch (val
& SPRD_PMIC_CHG_TYPE_MASK
) {
80 case SPRD_PMIC_CDP_TYPE
:
83 case SPRD_PMIC_DCP_TYPE
:
86 case SPRD_PMIC_SDP_TYPE
:
96 EXPORT_SYMBOL_GPL(sprd_pmic_detect_charger_type
);
98 static int sprd_pmic_spi_write(void *context
, const void *data
, size_t count
)
100 struct device
*dev
= context
;
101 struct spi_device
*spi
= to_spi_device(dev
);
103 return spi_write(spi
, data
, count
);
106 static int sprd_pmic_spi_read(void *context
,
107 const void *reg
, size_t reg_size
,
108 void *val
, size_t val_size
)
110 struct device
*dev
= context
;
111 struct spi_device
*spi
= to_spi_device(dev
);
112 u32 rx_buf
[2] = { 0 };
115 /* Now we only support one PMIC register to read every time. */
116 if (reg_size
!= sizeof(u32
) || val_size
!= sizeof(u32
))
119 /* Copy address to read from into first element of SPI buffer. */
120 memcpy(rx_buf
, reg
, sizeof(u32
));
121 ret
= spi_read(spi
, rx_buf
, 1);
125 memcpy(val
, rx_buf
, val_size
);
129 static struct regmap_bus sprd_pmic_regmap
= {
130 .write
= sprd_pmic_spi_write
,
131 .read
= sprd_pmic_spi_read
,
132 .reg_format_endian_default
= REGMAP_ENDIAN_NATIVE
,
133 .val_format_endian_default
= REGMAP_ENDIAN_NATIVE
,
136 static const struct regmap_config sprd_pmic_config
= {
140 .max_register
= 0xffff,
143 static int sprd_pmic_probe(struct spi_device
*spi
)
145 struct sprd_pmic
*ddata
;
146 const struct sprd_pmic_data
*pdata
;
149 pdata
= of_device_get_match_data(&spi
->dev
);
151 dev_err(&spi
->dev
, "No matching driver data found\n");
155 ddata
= devm_kzalloc(&spi
->dev
, sizeof(*ddata
), GFP_KERNEL
);
159 ddata
->regmap
= devm_regmap_init(&spi
->dev
, &sprd_pmic_regmap
,
160 &spi
->dev
, &sprd_pmic_config
);
161 if (IS_ERR(ddata
->regmap
)) {
162 ret
= PTR_ERR(ddata
->regmap
);
163 dev_err(&spi
->dev
, "Failed to allocate register map %d\n", ret
);
167 spi_set_drvdata(spi
, ddata
);
168 ddata
->dev
= &spi
->dev
;
169 ddata
->irq
= spi
->irq
;
170 ddata
->pdata
= pdata
;
172 ddata
->irq_chip
.name
= dev_name(&spi
->dev
);
173 ddata
->irq_chip
.status_base
=
174 pdata
->irq_base
+ SPRD_PMIC_INT_MASK_STATUS
;
175 ddata
->irq_chip
.mask_base
= pdata
->irq_base
+ SPRD_PMIC_INT_EN
;
176 ddata
->irq_chip
.ack_base
= 0;
177 ddata
->irq_chip
.num_regs
= 1;
178 ddata
->irq_chip
.num_irqs
= pdata
->num_irqs
;
179 ddata
->irq_chip
.mask_invert
= true;
181 ddata
->irqs
= devm_kcalloc(&spi
->dev
,
182 pdata
->num_irqs
, sizeof(struct regmap_irq
),
187 ddata
->irq_chip
.irqs
= ddata
->irqs
;
188 for (i
= 0; i
< pdata
->num_irqs
; i
++)
189 ddata
->irqs
[i
].mask
= BIT(i
);
191 ret
= devm_regmap_add_irq_chip(&spi
->dev
, ddata
->regmap
, ddata
->irq
,
193 &ddata
->irq_chip
, &ddata
->irq_data
);
195 dev_err(&spi
->dev
, "Failed to add PMIC irq chip %d\n", ret
);
199 ret
= devm_of_platform_populate(&spi
->dev
);
201 dev_err(&spi
->dev
, "Failed to populate sub-devices %d\n", ret
);
205 device_init_wakeup(&spi
->dev
, true);
209 #ifdef CONFIG_PM_SLEEP
210 static int sprd_pmic_suspend(struct device
*dev
)
212 struct sprd_pmic
*ddata
= dev_get_drvdata(dev
);
214 if (device_may_wakeup(dev
))
215 enable_irq_wake(ddata
->irq
);
220 static int sprd_pmic_resume(struct device
*dev
)
222 struct sprd_pmic
*ddata
= dev_get_drvdata(dev
);
224 if (device_may_wakeup(dev
))
225 disable_irq_wake(ddata
->irq
);
231 static SIMPLE_DEV_PM_OPS(sprd_pmic_pm_ops
, sprd_pmic_suspend
, sprd_pmic_resume
);
233 static const struct of_device_id sprd_pmic_match
[] = {
234 { .compatible
= "sprd,sc2731", .data
= &sc2731_data
},
237 MODULE_DEVICE_TABLE(of
, sprd_pmic_match
);
239 static struct spi_driver sprd_pmic_driver
= {
241 .name
= "sc27xx-pmic",
242 .of_match_table
= sprd_pmic_match
,
243 .pm
= &sprd_pmic_pm_ops
,
245 .probe
= sprd_pmic_probe
,
248 static int __init
sprd_pmic_init(void)
250 return spi_register_driver(&sprd_pmic_driver
);
252 subsys_initcall(sprd_pmic_init
);
254 static void __exit
sprd_pmic_exit(void)
256 spi_unregister_driver(&sprd_pmic_driver
);
258 module_exit(sprd_pmic_exit
);
260 MODULE_LICENSE("GPL v2");
261 MODULE_DESCRIPTION("Spreadtrum SC27xx PMICs driver");
262 MODULE_AUTHOR("Baolin Wang <baolin.wang@spreadtrum.com>");