1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * Copyright (C) 2018 Spreadtrum Communications Inc. |
4 | */ |
5 | |
6 | #include <linux/device.h> |
7 | #include <linux/input.h> |
8 | #include <linux/mod_devicetable.h> |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/property.h> |
12 | #include <linux/regmap.h> |
13 | #include <linux/workqueue.h> |
14 | |
15 | #define CUR_DRV_CAL_SEL GENMASK(13, 12) |
16 | #define SLP_LDOVIBR_PD_EN BIT(9) |
17 | #define LDO_VIBR_PD BIT(8) |
18 | #define SC2730_CUR_DRV_CAL_SEL 0 |
19 | #define SC2730_SLP_LDOVIBR_PD_EN BIT(14) |
20 | #define SC2730_LDO_VIBR_PD BIT(13) |
21 | |
22 | struct sc27xx_vibra_data { |
23 | u32 cur_drv_cal_sel; |
24 | u32 slp_pd_en; |
25 | u32 ldo_pd; |
26 | }; |
27 | |
28 | struct vibra_info { |
29 | struct input_dev *input_dev; |
30 | struct work_struct play_work; |
31 | struct regmap *regmap; |
32 | const struct sc27xx_vibra_data *data; |
33 | u32 base; |
34 | u32 strength; |
35 | bool enabled; |
36 | }; |
37 | |
38 | static const struct sc27xx_vibra_data sc2731_data = { |
39 | .cur_drv_cal_sel = CUR_DRV_CAL_SEL, |
40 | .slp_pd_en = SLP_LDOVIBR_PD_EN, |
41 | .ldo_pd = LDO_VIBR_PD, |
42 | }; |
43 | |
44 | static const struct sc27xx_vibra_data sc2730_data = { |
45 | .cur_drv_cal_sel = SC2730_CUR_DRV_CAL_SEL, |
46 | .slp_pd_en = SC2730_SLP_LDOVIBR_PD_EN, |
47 | .ldo_pd = SC2730_LDO_VIBR_PD, |
48 | }; |
49 | |
50 | static const struct sc27xx_vibra_data sc2721_data = { |
51 | .cur_drv_cal_sel = CUR_DRV_CAL_SEL, |
52 | .slp_pd_en = SLP_LDOVIBR_PD_EN, |
53 | .ldo_pd = LDO_VIBR_PD, |
54 | }; |
55 | |
56 | static void sc27xx_vibra_set(struct vibra_info *info, bool on) |
57 | { |
58 | const struct sc27xx_vibra_data *data = info->data; |
59 | if (on) { |
60 | regmap_update_bits(map: info->regmap, reg: info->base, mask: data->ldo_pd, val: 0); |
61 | regmap_update_bits(map: info->regmap, reg: info->base, |
62 | mask: data->slp_pd_en, val: 0); |
63 | info->enabled = true; |
64 | } else { |
65 | regmap_update_bits(map: info->regmap, reg: info->base, mask: data->ldo_pd, |
66 | val: data->ldo_pd); |
67 | regmap_update_bits(map: info->regmap, reg: info->base, |
68 | mask: data->slp_pd_en, val: data->slp_pd_en); |
69 | info->enabled = false; |
70 | } |
71 | } |
72 | |
73 | static int sc27xx_vibra_hw_init(struct vibra_info *info) |
74 | { |
75 | const struct sc27xx_vibra_data *data = info->data; |
76 | |
77 | if (!data->cur_drv_cal_sel) |
78 | return 0; |
79 | |
80 | return regmap_update_bits(map: info->regmap, reg: info->base, |
81 | mask: data->cur_drv_cal_sel, val: 0); |
82 | } |
83 | |
84 | static void sc27xx_vibra_play_work(struct work_struct *work) |
85 | { |
86 | struct vibra_info *info = container_of(work, struct vibra_info, |
87 | play_work); |
88 | |
89 | if (info->strength && !info->enabled) |
90 | sc27xx_vibra_set(info, on: true); |
91 | else if (info->strength == 0 && info->enabled) |
92 | sc27xx_vibra_set(info, on: false); |
93 | } |
94 | |
95 | static int sc27xx_vibra_play(struct input_dev *input, void *data, |
96 | struct ff_effect *effect) |
97 | { |
98 | struct vibra_info *info = input_get_drvdata(dev: input); |
99 | |
100 | info->strength = effect->u.rumble.weak_magnitude; |
101 | schedule_work(work: &info->play_work); |
102 | |
103 | return 0; |
104 | } |
105 | |
106 | static void sc27xx_vibra_close(struct input_dev *input) |
107 | { |
108 | struct vibra_info *info = input_get_drvdata(dev: input); |
109 | |
110 | cancel_work_sync(work: &info->play_work); |
111 | if (info->enabled) |
112 | sc27xx_vibra_set(info, on: false); |
113 | } |
114 | |
115 | static int sc27xx_vibra_probe(struct platform_device *pdev) |
116 | { |
117 | struct vibra_info *info; |
118 | const struct sc27xx_vibra_data *data; |
119 | int error; |
120 | |
121 | data = device_get_match_data(dev: &pdev->dev); |
122 | if (!data) { |
123 | dev_err(&pdev->dev, "no matching driver data found\n" ); |
124 | return -EINVAL; |
125 | } |
126 | |
127 | info = devm_kzalloc(dev: &pdev->dev, size: sizeof(*info), GFP_KERNEL); |
128 | if (!info) |
129 | return -ENOMEM; |
130 | |
131 | info->regmap = dev_get_regmap(dev: pdev->dev.parent, NULL); |
132 | if (!info->regmap) { |
133 | dev_err(&pdev->dev, "failed to get vibrator regmap.\n" ); |
134 | return -ENODEV; |
135 | } |
136 | |
137 | error = device_property_read_u32(dev: &pdev->dev, propname: "reg" , val: &info->base); |
138 | if (error) { |
139 | dev_err(&pdev->dev, "failed to get vibrator base address.\n" ); |
140 | return error; |
141 | } |
142 | |
143 | info->input_dev = devm_input_allocate_device(&pdev->dev); |
144 | if (!info->input_dev) { |
145 | dev_err(&pdev->dev, "failed to allocate input device.\n" ); |
146 | return -ENOMEM; |
147 | } |
148 | |
149 | info->input_dev->name = "sc27xx:vibrator" ; |
150 | info->input_dev->id.version = 0; |
151 | info->input_dev->close = sc27xx_vibra_close; |
152 | info->data = data; |
153 | |
154 | input_set_drvdata(dev: info->input_dev, data: info); |
155 | input_set_capability(dev: info->input_dev, EV_FF, FF_RUMBLE); |
156 | INIT_WORK(&info->play_work, sc27xx_vibra_play_work); |
157 | info->enabled = false; |
158 | |
159 | error = sc27xx_vibra_hw_init(info); |
160 | if (error) { |
161 | dev_err(&pdev->dev, "failed to initialize the vibrator.\n" ); |
162 | return error; |
163 | } |
164 | |
165 | error = input_ff_create_memless(dev: info->input_dev, NULL, |
166 | play_effect: sc27xx_vibra_play); |
167 | if (error) { |
168 | dev_err(&pdev->dev, "failed to register vibrator to FF.\n" ); |
169 | return error; |
170 | } |
171 | |
172 | error = input_register_device(info->input_dev); |
173 | if (error) { |
174 | dev_err(&pdev->dev, "failed to register input device.\n" ); |
175 | return error; |
176 | } |
177 | |
178 | return 0; |
179 | } |
180 | |
181 | static const struct of_device_id sc27xx_vibra_of_match[] = { |
182 | { .compatible = "sprd,sc2721-vibrator" , .data = &sc2721_data }, |
183 | { .compatible = "sprd,sc2730-vibrator" , .data = &sc2730_data }, |
184 | { .compatible = "sprd,sc2731-vibrator" , .data = &sc2731_data }, |
185 | {} |
186 | }; |
187 | MODULE_DEVICE_TABLE(of, sc27xx_vibra_of_match); |
188 | |
189 | static struct platform_driver sc27xx_vibra_driver = { |
190 | .driver = { |
191 | .name = "sc27xx-vibrator" , |
192 | .of_match_table = sc27xx_vibra_of_match, |
193 | }, |
194 | .probe = sc27xx_vibra_probe, |
195 | }; |
196 | |
197 | module_platform_driver(sc27xx_vibra_driver); |
198 | |
199 | MODULE_DESCRIPTION("Spreadtrum SC27xx Vibrator Driver" ); |
200 | MODULE_LICENSE("GPL v2" ); |
201 | MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>" ); |
202 | |