619 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Azoteq IQS621/622 Ambient Light Sensors4 *5 * Copyright (C) 2019 Jeff LaBundy <jeff@labundy.com>6 */7 8#include <linux/device.h>9#include <linux/iio/events.h>10#include <linux/iio/iio.h>11#include <linux/kernel.h>12#include <linux/mfd/iqs62x.h>13#include <linux/module.h>14#include <linux/mutex.h>15#include <linux/notifier.h>16#include <linux/platform_device.h>17#include <linux/regmap.h>18 19#define IQS621_ALS_FLAGS_LIGHT BIT(7)20#define IQS621_ALS_FLAGS_RANGE GENMASK(3, 0)21 22#define IQS621_ALS_UI_OUT 0x1723 24#define IQS621_ALS_THRESH_DARK 0x8025#define IQS621_ALS_THRESH_LIGHT 0x8126 27#define IQS622_IR_RANGE 0x1528#define IQS622_IR_FLAGS 0x1629#define IQS622_IR_FLAGS_TOUCH BIT(1)30#define IQS622_IR_FLAGS_PROX BIT(0)31 32#define IQS622_IR_UI_OUT 0x1733 34#define IQS622_IR_THRESH_PROX 0x9135#define IQS622_IR_THRESH_TOUCH 0x9236 37struct iqs621_als_private {38 struct iqs62x_core *iqs62x;39 struct iio_dev *indio_dev;40 struct notifier_block notifier;41 struct mutex lock;42 bool light_en;43 bool range_en;44 bool prox_en;45 u8 als_flags;46 u8 ir_flags_mask;47 u8 ir_flags;48 u8 thresh_light;49 u8 thresh_dark;50 u8 thresh_prox;51};52 53static int iqs621_als_init(struct iqs621_als_private *iqs621_als)54{55 struct iqs62x_core *iqs62x = iqs621_als->iqs62x;56 unsigned int event_mask = 0;57 int ret;58 59 switch (iqs621_als->ir_flags_mask) {60 case IQS622_IR_FLAGS_TOUCH:61 ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_TOUCH,62 iqs621_als->thresh_prox);63 break;64 65 case IQS622_IR_FLAGS_PROX:66 ret = regmap_write(iqs62x->regmap, IQS622_IR_THRESH_PROX,67 iqs621_als->thresh_prox);68 break;69 70 default:71 ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT,72 iqs621_als->thresh_light);73 if (ret)74 return ret;75 76 ret = regmap_write(iqs62x->regmap, IQS621_ALS_THRESH_DARK,77 iqs621_als->thresh_dark);78 }79 80 if (ret)81 return ret;82 83 if (iqs621_als->light_en || iqs621_als->range_en)84 event_mask |= iqs62x->dev_desc->als_mask;85 86 if (iqs621_als->prox_en)87 event_mask |= iqs62x->dev_desc->ir_mask;88 89 return regmap_clear_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,90 event_mask);91}92 93static int iqs621_als_notifier(struct notifier_block *notifier,94 unsigned long event_flags, void *context)95{96 struct iqs62x_event_data *event_data = context;97 struct iqs621_als_private *iqs621_als;98 struct iio_dev *indio_dev;99 bool light_new, light_old;100 bool prox_new, prox_old;101 u8 range_new, range_old;102 s64 timestamp;103 int ret;104 105 iqs621_als = container_of(notifier, struct iqs621_als_private,106 notifier);107 indio_dev = iqs621_als->indio_dev;108 timestamp = iio_get_time_ns(indio_dev);109 110 mutex_lock(&iqs621_als->lock);111 112 if (event_flags & BIT(IQS62X_EVENT_SYS_RESET)) {113 ret = iqs621_als_init(iqs621_als);114 if (ret) {115 dev_err(indio_dev->dev.parent,116 "Failed to re-initialize device: %d\n", ret);117 ret = NOTIFY_BAD;118 } else {119 ret = NOTIFY_OK;120 }121 122 goto err_mutex;123 }124 125 if (!iqs621_als->light_en && !iqs621_als->range_en &&126 !iqs621_als->prox_en) {127 ret = NOTIFY_DONE;128 goto err_mutex;129 }130 131 /* IQS621 only */132 light_new = event_data->als_flags & IQS621_ALS_FLAGS_LIGHT;133 light_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_LIGHT;134 135 if (iqs621_als->light_en && light_new && !light_old)136 iio_push_event(indio_dev,137 IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,138 IIO_EV_TYPE_THRESH,139 IIO_EV_DIR_RISING),140 timestamp);141 else if (iqs621_als->light_en && !light_new && light_old)142 iio_push_event(indio_dev,143 IIO_UNMOD_EVENT_CODE(IIO_LIGHT, 0,144 IIO_EV_TYPE_THRESH,145 IIO_EV_DIR_FALLING),146 timestamp);147 148 /* IQS621 and IQS622 */149 range_new = event_data->als_flags & IQS621_ALS_FLAGS_RANGE;150 range_old = iqs621_als->als_flags & IQS621_ALS_FLAGS_RANGE;151 152 if (iqs621_als->range_en && (range_new > range_old))153 iio_push_event(indio_dev,154 IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,155 IIO_EV_TYPE_CHANGE,156 IIO_EV_DIR_RISING),157 timestamp);158 else if (iqs621_als->range_en && (range_new < range_old))159 iio_push_event(indio_dev,160 IIO_UNMOD_EVENT_CODE(IIO_INTENSITY, 0,161 IIO_EV_TYPE_CHANGE,162 IIO_EV_DIR_FALLING),163 timestamp);164 165 /* IQS622 only */166 prox_new = event_data->ir_flags & iqs621_als->ir_flags_mask;167 prox_old = iqs621_als->ir_flags & iqs621_als->ir_flags_mask;168 169 if (iqs621_als->prox_en && prox_new && !prox_old)170 iio_push_event(indio_dev,171 IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,172 IIO_EV_TYPE_THRESH,173 IIO_EV_DIR_RISING),174 timestamp);175 else if (iqs621_als->prox_en && !prox_new && prox_old)176 iio_push_event(indio_dev,177 IIO_UNMOD_EVENT_CODE(IIO_PROXIMITY, 0,178 IIO_EV_TYPE_THRESH,179 IIO_EV_DIR_FALLING),180 timestamp);181 182 iqs621_als->als_flags = event_data->als_flags;183 iqs621_als->ir_flags = event_data->ir_flags;184 ret = NOTIFY_OK;185 186err_mutex:187 mutex_unlock(&iqs621_als->lock);188 189 return ret;190}191 192static void iqs621_als_notifier_unregister(void *context)193{194 struct iqs621_als_private *iqs621_als = context;195 struct iio_dev *indio_dev = iqs621_als->indio_dev;196 int ret;197 198 ret = blocking_notifier_chain_unregister(&iqs621_als->iqs62x->nh,199 &iqs621_als->notifier);200 if (ret)201 dev_err(indio_dev->dev.parent,202 "Failed to unregister notifier: %d\n", ret);203}204 205static int iqs621_als_read_raw(struct iio_dev *indio_dev,206 struct iio_chan_spec const *chan,207 int *val, int *val2, long mask)208{209 struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);210 struct iqs62x_core *iqs62x = iqs621_als->iqs62x;211 int ret;212 __le16 val_buf;213 214 switch (chan->type) {215 case IIO_INTENSITY:216 ret = regmap_read(iqs62x->regmap, chan->address, val);217 if (ret)218 return ret;219 220 *val &= IQS621_ALS_FLAGS_RANGE;221 return IIO_VAL_INT;222 223 case IIO_PROXIMITY:224 case IIO_LIGHT:225 ret = regmap_raw_read(iqs62x->regmap, chan->address, &val_buf,226 sizeof(val_buf));227 if (ret)228 return ret;229 230 *val = le16_to_cpu(val_buf);231 return IIO_VAL_INT;232 233 default:234 return -EINVAL;235 }236}237 238static int iqs621_als_read_event_config(struct iio_dev *indio_dev,239 const struct iio_chan_spec *chan,240 enum iio_event_type type,241 enum iio_event_direction dir)242{243 struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);244 int ret;245 246 mutex_lock(&iqs621_als->lock);247 248 switch (chan->type) {249 case IIO_LIGHT:250 ret = iqs621_als->light_en;251 break;252 253 case IIO_INTENSITY:254 ret = iqs621_als->range_en;255 break;256 257 case IIO_PROXIMITY:258 ret = iqs621_als->prox_en;259 break;260 261 default:262 ret = -EINVAL;263 }264 265 mutex_unlock(&iqs621_als->lock);266 267 return ret;268}269 270static int iqs621_als_write_event_config(struct iio_dev *indio_dev,271 const struct iio_chan_spec *chan,272 enum iio_event_type type,273 enum iio_event_direction dir,274 int state)275{276 struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);277 struct iqs62x_core *iqs62x = iqs621_als->iqs62x;278 unsigned int val;279 int ret;280 281 mutex_lock(&iqs621_als->lock);282 283 ret = regmap_read(iqs62x->regmap, iqs62x->dev_desc->als_flags, &val);284 if (ret)285 goto err_mutex;286 iqs621_als->als_flags = val;287 288 switch (chan->type) {289 case IIO_LIGHT:290 ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,291 iqs62x->dev_desc->als_mask,292 iqs621_als->range_en || state ? 0 :293 0xFF);294 if (!ret)295 iqs621_als->light_en = state;296 break;297 298 case IIO_INTENSITY:299 ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,300 iqs62x->dev_desc->als_mask,301 iqs621_als->light_en || state ? 0 :302 0xFF);303 if (!ret)304 iqs621_als->range_en = state;305 break;306 307 case IIO_PROXIMITY:308 ret = regmap_read(iqs62x->regmap, IQS622_IR_FLAGS, &val);309 if (ret)310 goto err_mutex;311 iqs621_als->ir_flags = val;312 313 ret = regmap_update_bits(iqs62x->regmap, IQS620_GLBL_EVENT_MASK,314 iqs62x->dev_desc->ir_mask,315 state ? 0 : 0xFF);316 if (!ret)317 iqs621_als->prox_en = state;318 break;319 320 default:321 ret = -EINVAL;322 }323 324err_mutex:325 mutex_unlock(&iqs621_als->lock);326 327 return ret;328}329 330static int iqs621_als_read_event_value(struct iio_dev *indio_dev,331 const struct iio_chan_spec *chan,332 enum iio_event_type type,333 enum iio_event_direction dir,334 enum iio_event_info info,335 int *val, int *val2)336{337 struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);338 int ret = IIO_VAL_INT;339 340 mutex_lock(&iqs621_als->lock);341 342 switch (dir) {343 case IIO_EV_DIR_RISING:344 *val = iqs621_als->thresh_light * 16;345 break;346 347 case IIO_EV_DIR_FALLING:348 *val = iqs621_als->thresh_dark * 4;349 break;350 351 case IIO_EV_DIR_EITHER:352 if (iqs621_als->ir_flags_mask == IQS622_IR_FLAGS_TOUCH)353 *val = iqs621_als->thresh_prox * 4;354 else355 *val = iqs621_als->thresh_prox;356 break;357 358 default:359 ret = -EINVAL;360 }361 362 mutex_unlock(&iqs621_als->lock);363 364 return ret;365}366 367static int iqs621_als_write_event_value(struct iio_dev *indio_dev,368 const struct iio_chan_spec *chan,369 enum iio_event_type type,370 enum iio_event_direction dir,371 enum iio_event_info info,372 int val, int val2)373{374 struct iqs621_als_private *iqs621_als = iio_priv(indio_dev);375 struct iqs62x_core *iqs62x = iqs621_als->iqs62x;376 unsigned int thresh_reg, thresh_val;377 u8 ir_flags_mask, *thresh_cache;378 int ret = -EINVAL;379 380 mutex_lock(&iqs621_als->lock);381 382 switch (dir) {383 case IIO_EV_DIR_RISING:384 thresh_reg = IQS621_ALS_THRESH_LIGHT;385 thresh_val = val / 16;386 387 thresh_cache = &iqs621_als->thresh_light;388 ir_flags_mask = 0;389 break;390 391 case IIO_EV_DIR_FALLING:392 thresh_reg = IQS621_ALS_THRESH_DARK;393 thresh_val = val / 4;394 395 thresh_cache = &iqs621_als->thresh_dark;396 ir_flags_mask = 0;397 break;398 399 case IIO_EV_DIR_EITHER:400 /*401 * The IQS622 supports two detection thresholds, both measured402 * in the same arbitrary units reported by read_raw: proximity403 * (0 through 255 in steps of 1), and touch (0 through 1020 in404 * steps of 4).405 *406 * Based on the single detection threshold chosen by the user,407 * select the hardware threshold that gives the best trade-off408 * between range and resolution.409 *410 * By default, the close-range (but coarse) touch threshold is411 * chosen during probe.412 */413 switch (val) {414 case 0 ... 255:415 thresh_reg = IQS622_IR_THRESH_PROX;416 thresh_val = val;417 418 ir_flags_mask = IQS622_IR_FLAGS_PROX;419 break;420 421 case 256 ... 1020:422 thresh_reg = IQS622_IR_THRESH_TOUCH;423 thresh_val = val / 4;424 425 ir_flags_mask = IQS622_IR_FLAGS_TOUCH;426 break;427 428 default:429 goto err_mutex;430 }431 432 thresh_cache = &iqs621_als->thresh_prox;433 break;434 435 default:436 goto err_mutex;437 }438 439 if (thresh_val > 0xFF)440 goto err_mutex;441 442 ret = regmap_write(iqs62x->regmap, thresh_reg, thresh_val);443 if (ret)444 goto err_mutex;445 446 *thresh_cache = thresh_val;447 iqs621_als->ir_flags_mask = ir_flags_mask;448 449err_mutex:450 mutex_unlock(&iqs621_als->lock);451 452 return ret;453}454 455static const struct iio_info iqs621_als_info = {456 .read_raw = &iqs621_als_read_raw,457 .read_event_config = iqs621_als_read_event_config,458 .write_event_config = iqs621_als_write_event_config,459 .read_event_value = iqs621_als_read_event_value,460 .write_event_value = iqs621_als_write_event_value,461};462 463static const struct iio_event_spec iqs621_als_range_events[] = {464 {465 .type = IIO_EV_TYPE_CHANGE,466 .dir = IIO_EV_DIR_EITHER,467 .mask_separate = BIT(IIO_EV_INFO_ENABLE),468 },469};470 471static const struct iio_event_spec iqs621_als_light_events[] = {472 {473 .type = IIO_EV_TYPE_THRESH,474 .dir = IIO_EV_DIR_EITHER,475 .mask_separate = BIT(IIO_EV_INFO_ENABLE),476 },477 {478 .type = IIO_EV_TYPE_THRESH,479 .dir = IIO_EV_DIR_RISING,480 .mask_separate = BIT(IIO_EV_INFO_VALUE),481 },482 {483 .type = IIO_EV_TYPE_THRESH,484 .dir = IIO_EV_DIR_FALLING,485 .mask_separate = BIT(IIO_EV_INFO_VALUE),486 },487};488 489static const struct iio_chan_spec iqs621_als_channels[] = {490 {491 .type = IIO_INTENSITY,492 .address = IQS621_ALS_FLAGS,493 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),494 .event_spec = iqs621_als_range_events,495 .num_event_specs = ARRAY_SIZE(iqs621_als_range_events),496 },497 {498 .type = IIO_LIGHT,499 .address = IQS621_ALS_UI_OUT,500 .info_mask_separate = BIT(IIO_CHAN_INFO_PROCESSED),501 .event_spec = iqs621_als_light_events,502 .num_event_specs = ARRAY_SIZE(iqs621_als_light_events),503 },504};505 506static const struct iio_event_spec iqs622_als_prox_events[] = {507 {508 .type = IIO_EV_TYPE_THRESH,509 .dir = IIO_EV_DIR_EITHER,510 .mask_separate = BIT(IIO_EV_INFO_ENABLE) |511 BIT(IIO_EV_INFO_VALUE),512 },513};514 515static const struct iio_chan_spec iqs622_als_channels[] = {516 {517 .type = IIO_INTENSITY,518 .channel2 = IIO_MOD_LIGHT_BOTH,519 .address = IQS622_ALS_FLAGS,520 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),521 .event_spec = iqs621_als_range_events,522 .num_event_specs = ARRAY_SIZE(iqs621_als_range_events),523 .modified = true,524 },525 {526 .type = IIO_INTENSITY,527 .channel2 = IIO_MOD_LIGHT_IR,528 .address = IQS622_IR_RANGE,529 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),530 .modified = true,531 },532 {533 .type = IIO_PROXIMITY,534 .address = IQS622_IR_UI_OUT,535 .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),536 .event_spec = iqs622_als_prox_events,537 .num_event_specs = ARRAY_SIZE(iqs622_als_prox_events),538 },539};540 541static int iqs621_als_probe(struct platform_device *pdev)542{543 struct iqs62x_core *iqs62x = dev_get_drvdata(pdev->dev.parent);544 struct iqs621_als_private *iqs621_als;545 struct iio_dev *indio_dev;546 unsigned int val;547 int ret;548 549 indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*iqs621_als));550 if (!indio_dev)551 return -ENOMEM;552 553 iqs621_als = iio_priv(indio_dev);554 iqs621_als->iqs62x = iqs62x;555 iqs621_als->indio_dev = indio_dev;556 557 if (iqs62x->dev_desc->prod_num == IQS622_PROD_NUM) {558 ret = regmap_read(iqs62x->regmap, IQS622_IR_THRESH_TOUCH,559 &val);560 if (ret)561 return ret;562 iqs621_als->thresh_prox = val;563 iqs621_als->ir_flags_mask = IQS622_IR_FLAGS_TOUCH;564 565 indio_dev->channels = iqs622_als_channels;566 indio_dev->num_channels = ARRAY_SIZE(iqs622_als_channels);567 } else {568 ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_LIGHT,569 &val);570 if (ret)571 return ret;572 iqs621_als->thresh_light = val;573 574 ret = regmap_read(iqs62x->regmap, IQS621_ALS_THRESH_DARK,575 &val);576 if (ret)577 return ret;578 iqs621_als->thresh_dark = val;579 580 indio_dev->channels = iqs621_als_channels;581 indio_dev->num_channels = ARRAY_SIZE(iqs621_als_channels);582 }583 584 indio_dev->modes = INDIO_DIRECT_MODE;585 indio_dev->name = iqs62x->dev_desc->dev_name;586 indio_dev->info = &iqs621_als_info;587 588 mutex_init(&iqs621_als->lock);589 590 iqs621_als->notifier.notifier_call = iqs621_als_notifier;591 ret = blocking_notifier_chain_register(&iqs621_als->iqs62x->nh,592 &iqs621_als->notifier);593 if (ret) {594 dev_err(&pdev->dev, "Failed to register notifier: %d\n", ret);595 return ret;596 }597 598 ret = devm_add_action_or_reset(&pdev->dev,599 iqs621_als_notifier_unregister,600 iqs621_als);601 if (ret)602 return ret;603 604 return devm_iio_device_register(&pdev->dev, indio_dev);605}606 607static struct platform_driver iqs621_als_platform_driver = {608 .driver = {609 .name = "iqs621-als",610 },611 .probe = iqs621_als_probe,612};613module_platform_driver(iqs621_als_platform_driver);614 615MODULE_AUTHOR("Jeff LaBundy <jeff@labundy.com>");616MODULE_DESCRIPTION("Azoteq IQS621/622 Ambient Light Sensors");617MODULE_LICENSE("GPL");618MODULE_ALIAS("platform:iqs621-als");619