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 /* 171 * The power-button is connected to ground so a falling edge (dbf) 172 * means it is pressed. 173 */ 174 if (irq == axp20x_pek->irq_dbf) 175 input_report_key(idev, KEY_POWER, true); 176 else if (irq == axp20x_pek->irq_dbr) 177 input_report_key(idev, KEY_POWER, false); 178 179 input_sync(idev); 180 181 return IRQ_HANDLED; 182 } 183 184 static void axp20x_remove_sysfs_group(void *_data) 185 { 186 struct device *dev = _data; 187 188 sysfs_remove_group(&dev->kobj, &axp20x_attribute_group); 189 } 190 191 static int axp20x_pek_probe(struct platform_device *pdev) 192 { 193 struct axp20x_pek *axp20x_pek; 194 struct axp20x_dev *axp20x; 195 struct input_dev *idev; 196 int error; 197 198 axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek), 199 GFP_KERNEL); 200 if (!axp20x_pek) 201 return -ENOMEM; 202 203 axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent); 204 axp20x = axp20x_pek->axp20x; 205 206 axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR"); 207 if (axp20x_pek->irq_dbr < 0) { 208 dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n", 209 axp20x_pek->irq_dbr); 210 return axp20x_pek->irq_dbr; 211 } 212 axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc, 213 axp20x_pek->irq_dbr); 214 215 axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF"); 216 if (axp20x_pek->irq_dbf < 0) { 217 dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n", 218 axp20x_pek->irq_dbf); 219 return axp20x_pek->irq_dbf; 220 } 221 axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc, 222 axp20x_pek->irq_dbf); 223 224 axp20x_pek->input = devm_input_allocate_device(&pdev->dev); 225 if (!axp20x_pek->input) 226 return -ENOMEM; 227 228 idev = axp20x_pek->input; 229 230 idev->name = "axp20x-pek"; 231 idev->phys = "m1kbd/input2"; 232 idev->dev.parent = &pdev->dev; 233 234 input_set_capability(idev, EV_KEY, KEY_POWER); 235 236 input_set_drvdata(idev, axp20x_pek); 237 238 error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr, 239 axp20x_pek_irq, 0, 240 "axp20x-pek-dbr", idev); 241 if (error < 0) { 242 dev_err(axp20x->dev, "Failed to request dbr IRQ#%d: %d\n", 243 axp20x_pek->irq_dbr, error); 244 return error; 245 } 246 247 error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf, 248 axp20x_pek_irq, 0, 249 "axp20x-pek-dbf", idev); 250 if (error < 0) { 251 dev_err(axp20x->dev, "Failed to request dbf IRQ#%d: %d\n", 252 axp20x_pek->irq_dbf, error); 253 return error; 254 } 255 256 error = sysfs_create_group(&pdev->dev.kobj, &axp20x_attribute_group); 257 if (error) { 258 dev_err(axp20x->dev, "Failed to create sysfs attributes: %d\n", 259 error); 260 return error; 261 } 262 263 error = devm_add_action(&pdev->dev, 264 axp20x_remove_sysfs_group, &pdev->dev); 265 if (error) { 266 axp20x_remove_sysfs_group(&pdev->dev); 267 dev_err(&pdev->dev, "Failed to add sysfs cleanup action: %d\n", 268 error); 269 return error; 270 } 271 272 error = input_register_device(idev); 273 if (error) { 274 dev_err(axp20x->dev, "Can't register input device: %d\n", 275 error); 276 return error; 277 } 278 279 platform_set_drvdata(pdev, axp20x_pek); 280 281 return 0; 282 } 283 284 static struct platform_driver axp20x_pek_driver = { 285 .probe = axp20x_pek_probe, 286 .driver = { 287 .name = "axp20x-pek", 288 }, 289 }; 290 module_platform_driver(axp20x_pek_driver); 291 292 MODULE_DESCRIPTION("axp20x Power Button"); 293 MODULE_AUTHOR("Carlo Caione <carlo@caione.org>"); 294 MODULE_LICENSE("GPL"); 295 MODULE_ALIAS("platform:axp20x-pek"); 296