1 /* 2 * axp20x power button driver. 3 * 4 * Copyright (C) 2013 Carlo Caione <carlo@caione.org> 5 * 6 * This file is subject to the terms and conditions of the GNU General 7 * Public License. See the file "COPYING" in the main directory of this 8 * archive for more details. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 */ 15 16 #include <linux/errno.h> 17 #include <linux/irq.h> 18 #include <linux/init.h> 19 #include <linux/input.h> 20 #include <linux/interrupt.h> 21 #include <linux/kernel.h> 22 #include <linux/mfd/axp20x.h> 23 #include <linux/module.h> 24 #include <linux/platform_device.h> 25 #include <linux/regmap.h> 26 #include <linux/slab.h> 27 28 #define AXP20X_PEK_STARTUP_MASK (0xc0) 29 #define AXP20X_PEK_SHUTDOWN_MASK (0x03) 30 31 struct axp20x_pek { 32 struct axp20x_dev *axp20x; 33 struct input_dev *input; 34 int irq_dbr; 35 int irq_dbf; 36 }; 37 38 struct axp20x_time { 39 unsigned int time; 40 unsigned int idx; 41 }; 42 43 static const struct axp20x_time startup_time[] = { 44 { .time = 128, .idx = 0 }, 45 { .time = 1000, .idx = 2 }, 46 { .time = 3000, .idx = 1 }, 47 { .time = 2000, .idx = 3 }, 48 }; 49 50 static const struct axp20x_time shutdown_time[] = { 51 { .time = 4000, .idx = 0 }, 52 { .time = 6000, .idx = 1 }, 53 { .time = 8000, .idx = 2 }, 54 { .time = 10000, .idx = 3 }, 55 }; 56 57 struct axp20x_pek_ext_attr { 58 const struct axp20x_time *p_time; 59 unsigned int mask; 60 }; 61 62 static struct axp20x_pek_ext_attr axp20x_pek_startup_ext_attr = { 63 .p_time = startup_time, 64 .mask = AXP20X_PEK_STARTUP_MASK, 65 }; 66 67 static struct axp20x_pek_ext_attr axp20x_pek_shutdown_ext_attr = { 68 .p_time = shutdown_time, 69 .mask = AXP20X_PEK_SHUTDOWN_MASK, 70 }; 71 72 static struct axp20x_pek_ext_attr *get_axp_ext_attr(struct device_attribute *attr) 73 { 74 return container_of(attr, struct dev_ext_attribute, attr)->var; 75 } 76 77 static ssize_t axp20x_show_ext_attr(struct device *dev, 78 struct device_attribute *attr, char *buf) 79 { 80 struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); 81 struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr); 82 unsigned int val; 83 int ret, i; 84 85 ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val); 86 if (ret != 0) 87 return ret; 88 89 val &= axp20x_ea->mask; 90 val >>= ffs(axp20x_ea->mask) - 1; 91 92 for (i = 0; i < 4; i++) 93 if (val == axp20x_ea->p_time[i].idx) 94 val = axp20x_ea->p_time[i].time; 95 96 return sprintf(buf, "%u\n", val); 97 } 98 99 static ssize_t axp20x_store_ext_attr(struct device *dev, 100 struct device_attribute *attr, 101 const char *buf, size_t count) 102 { 103 struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev); 104 struct axp20x_pek_ext_attr *axp20x_ea = get_axp_ext_attr(attr); 105 char val_str[20]; 106 size_t len; 107 int ret, i; 108 unsigned int val, idx = 0; 109 unsigned int best_err = UINT_MAX; 110 111 val_str[sizeof(val_str) - 1] = '\0'; 112 strncpy(val_str, buf, sizeof(val_str) - 1); 113 len = strlen(val_str); 114 115 if (len && val_str[len - 1] == '\n') 116 val_str[len - 1] = '\0'; 117 118 ret = kstrtouint(val_str, 10, &val); 119 if (ret) 120 return ret; 121 122 for (i = 3; i >= 0; i--) { 123 unsigned int err; 124 125 err = abs(axp20x_ea->p_time[i].time - val); 126 if (err < best_err) { 127 best_err = err; 128 idx = axp20x_ea->p_time[i].idx; 129 } 130 131 if (!err) 132 break; 133 } 134 135 idx <<= ffs(axp20x_ea->mask) - 1; 136 ret = regmap_update_bits(axp20x_pek->axp20x->regmap, 137 AXP20X_PEK_KEY, 138 axp20x_ea->mask, idx); 139 if (ret != 0) 140 return -EINVAL; 141 142 return count; 143 } 144 145 static struct dev_ext_attribute axp20x_dev_attr_startup = { 146 .attr = __ATTR(startup, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr), 147 .var = &axp20x_pek_startup_ext_attr, 148 }; 149 150 static struct dev_ext_attribute axp20x_dev_attr_shutdown = { 151 .attr = __ATTR(shutdown, 0644, axp20x_show_ext_attr, axp20x_store_ext_attr), 152 .var = &axp20x_pek_shutdown_ext_attr, 153 }; 154 155 static struct attribute *axp20x_attributes[] = { 156 &axp20x_dev_attr_startup.attr.attr, 157 &axp20x_dev_attr_shutdown.attr.attr, 158 NULL, 159 }; 160 161 static const struct attribute_group axp20x_attribute_group = { 162 .attrs = axp20x_attributes, 163 }; 164 165 static irqreturn_t axp20x_pek_irq(int irq, void *pwr) 166 { 167 struct input_dev *idev = pwr; 168 struct axp20x_pek *axp20x_pek = input_get_drvdata(idev); 169 170 if (irq == axp20x_pek->irq_dbr) 171 input_report_key(idev, KEY_POWER, true); 172 else if (irq == axp20x_pek->irq_dbf) 173 input_report_key(idev, KEY_POWER, false); 174 175 input_sync(idev); 176 177 return IRQ_HANDLED; 178 } 179 180 static void axp20x_remove_sysfs_group(void *_data) 181 { 182 struct device *dev = _data; 183 184 sysfs_remove_group(&dev->kobj, &axp20x_attribute_group); 185 } 186 187 static int axp20x_pek_probe(struct platform_device *pdev) 188 { 189 struct axp20x_pek *axp20x_pek; 190 struct axp20x_dev *axp20x; 191 struct input_dev *idev; 192 int error; 193 194 axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek), 195 GFP_KERNEL); 196 if (!axp20x_pek) 197 return -ENOMEM; 198 199 axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent); 200 axp20x = axp20x_pek->axp20x; 201 202 axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR"); 203 if (axp20x_pek->irq_dbr < 0) { 204 dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n", 205 axp20x_pek->irq_dbr); 206 return axp20x_pek->irq_dbr; 207 } 208 axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc, 209 axp20x_pek->irq_dbr); 210 211 axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF"); 212 if (axp20x_pek->irq_dbf < 0) { 213 dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n", 214 axp20x_pek->irq_dbf); 215 return axp20x_pek->irq_dbf; 216 } 217 axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc, 218 axp20x_pek->irq_dbf); 219 220 axp20x_pek->input = devm_input_allocate_device(&pdev->dev); 221 if (!axp20x_pek->input) 222 return -ENOMEM; 223 224 idev = axp20x_pek->input; 225 226 idev->name = "axp20x-pek"; 227 idev->phys = "m1kbd/input2"; 228 idev->dev.parent = &pdev->dev; 229 230 input_set_capability(idev, EV_KEY, KEY_POWER); 231 232 input_set_drvdata(idev, axp20x_pek); 233 234 error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr, 235 axp20x_pek_irq, 0, 236 "axp20x-pek-dbr", idev); 237 if (error < 0) { 238 dev_err(axp20x->dev, "Failed to request dbr IRQ#%d: %d\n", 239 axp20x_pek->irq_dbr, error); 240 return error; 241 } 242 243 error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf, 244 axp20x_pek_irq, 0, 245 "axp20x-pek-dbf", idev); 246 if (error < 0) { 247 dev_err(axp20x->dev, "Failed to request dbf IRQ#%d: %d\n", 248 axp20x_pek->irq_dbf, error); 249 return error; 250 } 251 252 error = sysfs_create_group(&pdev->dev.kobj, &axp20x_attribute_group); 253 if (error) { 254 dev_err(axp20x->dev, "Failed to create sysfs attributes: %d\n", 255 error); 256 return error; 257 } 258 259 error = devm_add_action(&pdev->dev, 260 axp20x_remove_sysfs_group, &pdev->dev); 261 if (error) { 262 axp20x_remove_sysfs_group(&pdev->dev); 263 dev_err(&pdev->dev, "Failed to add sysfs cleanup action: %d\n", 264 error); 265 return error; 266 } 267 268 error = input_register_device(idev); 269 if (error) { 270 dev_err(axp20x->dev, "Can't register input device: %d\n", 271 error); 272 return error; 273 } 274 275 platform_set_drvdata(pdev, axp20x_pek); 276 277 return 0; 278 } 279 280 static struct platform_driver axp20x_pek_driver = { 281 .probe = axp20x_pek_probe, 282 .driver = { 283 .name = "axp20x-pek", 284 }, 285 }; 286 module_platform_driver(axp20x_pek_driver); 287 288 MODULE_DESCRIPTION("axp20x Power Button"); 289 MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 290 MODULE_LICENSE("GPL"); 291