1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Device driver for MFD hi655x PMIC |
4 | * |
5 | * Copyright (c) 2016 HiSilicon Ltd. |
6 | * |
7 | * Authors: |
8 | * Chen Feng <puck.chen@hisilicon.com> |
9 | * Fei Wang <w.f@huawei.com> |
10 | */ |
11 | |
12 | #include <linux/io.h> |
13 | #include <linux/interrupt.h> |
14 | #include <linux/init.h> |
15 | #include <linux/mfd/core.h> |
16 | #include <linux/mfd/hi655x-pmic.h> |
17 | #include <linux/module.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/mod_devicetable.h> |
20 | #include <linux/platform_device.h> |
21 | #include <linux/regmap.h> |
22 | |
23 | static const struct regmap_irq hi655x_irqs[] = { |
24 | { .reg_offset = 0, .mask = OTMP_D1R_INT_MASK }, |
25 | { .reg_offset = 0, .mask = VSYS_2P5_R_INT_MASK }, |
26 | { .reg_offset = 0, .mask = VSYS_UV_D3R_INT_MASK }, |
27 | { .reg_offset = 0, .mask = VSYS_6P0_D200UR_INT_MASK }, |
28 | { .reg_offset = 0, .mask = PWRON_D4SR_INT_MASK }, |
29 | { .reg_offset = 0, .mask = PWRON_D20F_INT_MASK }, |
30 | { .reg_offset = 0, .mask = PWRON_D20R_INT_MASK }, |
31 | { .reg_offset = 0, .mask = RESERVE_INT_MASK }, |
32 | }; |
33 | |
34 | static const struct regmap_irq_chip hi655x_irq_chip = { |
35 | .name = "hi655x-pmic" , |
36 | .irqs = hi655x_irqs, |
37 | .num_regs = 1, |
38 | .num_irqs = ARRAY_SIZE(hi655x_irqs), |
39 | .status_base = HI655X_IRQ_STAT_BASE, |
40 | .ack_base = HI655X_IRQ_STAT_BASE, |
41 | .mask_base = HI655X_IRQ_MASK_BASE, |
42 | }; |
43 | |
44 | static struct regmap_config hi655x_regmap_config = { |
45 | .reg_bits = 32, |
46 | .reg_stride = HI655X_STRIDE, |
47 | .val_bits = 8, |
48 | .max_register = HI655X_BUS_ADDR(0x400) - HI655X_STRIDE, |
49 | }; |
50 | |
51 | static const struct resource pwrkey_resources[] = { |
52 | { |
53 | .name = "down" , |
54 | .start = PWRON_D20R_INT, |
55 | .end = PWRON_D20R_INT, |
56 | .flags = IORESOURCE_IRQ, |
57 | }, { |
58 | .name = "up" , |
59 | .start = PWRON_D20F_INT, |
60 | .end = PWRON_D20F_INT, |
61 | .flags = IORESOURCE_IRQ, |
62 | }, { |
63 | .name = "hold 4s" , |
64 | .start = PWRON_D4SR_INT, |
65 | .end = PWRON_D4SR_INT, |
66 | .flags = IORESOURCE_IRQ, |
67 | }, |
68 | }; |
69 | |
70 | static const struct mfd_cell hi655x_pmic_devs[] = { |
71 | { |
72 | .name = "hi65xx-powerkey" , |
73 | .num_resources = ARRAY_SIZE(pwrkey_resources), |
74 | .resources = &pwrkey_resources[0], |
75 | }, |
76 | { .name = "hi655x-regulator" , }, |
77 | { .name = "hi655x-clk" , }, |
78 | }; |
79 | |
80 | static void hi655x_local_irq_clear(struct regmap *map) |
81 | { |
82 | int i; |
83 | |
84 | regmap_write(map, HI655X_ANA_IRQM_BASE, HI655X_IRQ_CLR); |
85 | for (i = 0; i < HI655X_IRQ_ARRAY; i++) { |
86 | regmap_write(map, HI655X_IRQ_STAT_BASE + i * HI655X_STRIDE, |
87 | HI655X_IRQ_CLR); |
88 | } |
89 | } |
90 | |
91 | static int hi655x_pmic_probe(struct platform_device *pdev) |
92 | { |
93 | int ret; |
94 | struct hi655x_pmic *pmic; |
95 | struct device *dev = &pdev->dev; |
96 | void __iomem *base; |
97 | |
98 | pmic = devm_kzalloc(dev, size: sizeof(*pmic), GFP_KERNEL); |
99 | if (!pmic) |
100 | return -ENOMEM; |
101 | pmic->dev = dev; |
102 | |
103 | base = devm_platform_ioremap_resource(pdev, index: 0); |
104 | if (IS_ERR(ptr: base)) |
105 | return PTR_ERR(ptr: base); |
106 | |
107 | pmic->regmap = devm_regmap_init_mmio_clk(dev, NULL, base, |
108 | &hi655x_regmap_config); |
109 | if (IS_ERR(ptr: pmic->regmap)) |
110 | return PTR_ERR(ptr: pmic->regmap); |
111 | |
112 | regmap_read(map: pmic->regmap, HI655X_BUS_ADDR(HI655X_VER_REG), val: &pmic->ver); |
113 | if ((pmic->ver < PMU_VER_START) || (pmic->ver > PMU_VER_END)) { |
114 | dev_warn(dev, "PMU version %d unsupported\n" , pmic->ver); |
115 | return -EINVAL; |
116 | } |
117 | |
118 | hi655x_local_irq_clear(map: pmic->regmap); |
119 | |
120 | pmic->gpio = devm_gpiod_get_optional(dev, con_id: "pmic" , flags: GPIOD_IN); |
121 | if (IS_ERR(ptr: pmic->gpio)) |
122 | return dev_err_probe(dev, err: PTR_ERR(ptr: pmic->gpio), |
123 | fmt: "Failed to request hi655x pmic-gpio" ); |
124 | |
125 | ret = regmap_add_irq_chip(map: pmic->regmap, irq: gpiod_to_irq(desc: pmic->gpio), |
126 | IRQF_TRIGGER_LOW | IRQF_NO_SUSPEND, irq_base: 0, |
127 | chip: &hi655x_irq_chip, data: &pmic->irq_data); |
128 | if (ret) { |
129 | dev_err(dev, "Failed to obtain 'hi655x_pmic_irq' %d\n" , ret); |
130 | return ret; |
131 | } |
132 | |
133 | platform_set_drvdata(pdev, data: pmic); |
134 | |
135 | ret = mfd_add_devices(parent: dev, PLATFORM_DEVID_AUTO, cells: hi655x_pmic_devs, |
136 | ARRAY_SIZE(hi655x_pmic_devs), NULL, irq_base: 0, |
137 | irq_domain: regmap_irq_get_domain(data: pmic->irq_data)); |
138 | if (ret) { |
139 | dev_err(dev, "Failed to register device %d\n" , ret); |
140 | regmap_del_irq_chip(irq: gpiod_to_irq(desc: pmic->gpio), data: pmic->irq_data); |
141 | return ret; |
142 | } |
143 | |
144 | return 0; |
145 | } |
146 | |
147 | static void hi655x_pmic_remove(struct platform_device *pdev) |
148 | { |
149 | struct hi655x_pmic *pmic = platform_get_drvdata(pdev); |
150 | |
151 | regmap_del_irq_chip(irq: gpiod_to_irq(desc: pmic->gpio), data: pmic->irq_data); |
152 | mfd_remove_devices(parent: &pdev->dev); |
153 | } |
154 | |
155 | static const struct of_device_id hi655x_pmic_match[] = { |
156 | { .compatible = "hisilicon,hi655x-pmic" , }, |
157 | {}, |
158 | }; |
159 | MODULE_DEVICE_TABLE(of, hi655x_pmic_match); |
160 | |
161 | static struct platform_driver hi655x_pmic_driver = { |
162 | .driver = { |
163 | .name = "hi655x-pmic" , |
164 | .of_match_table = hi655x_pmic_match, |
165 | }, |
166 | .probe = hi655x_pmic_probe, |
167 | .remove_new = hi655x_pmic_remove, |
168 | }; |
169 | module_platform_driver(hi655x_pmic_driver); |
170 | |
171 | MODULE_AUTHOR("Chen Feng <puck.chen@hisilicon.com>" ); |
172 | MODULE_DESCRIPTION("Hisilicon hi655x PMIC driver" ); |
173 | MODULE_LICENSE("GPL v2" ); |
174 | |