Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iio: accel: adxl313: add AC coupled activity/inactivity events

Introduce AC-coupled activity and inactivity as MAG_ADAPTIVE events.
This adds a new set of threshold and duration configuration options,
ensures proper handling of event disabling, and extends the use of the
link bit to support complementary event configurations.

For example, either ACTIVITY or ACTIVITY_AC can be enabled, but only the
most recently set configuration will remain active. Disabling ACTIVITY
will have no effect if ACTIVITY_AC is currently enabled, as the event
types must match (i.e., ACTIVITY_AC must be explicitly disabled). When
either INACTIVITY or INACTIVITY_AC is enabled alongside an activity
event, the link bit is set.

With the link bit and auto-sleep enabled, activity and inactivity events
represent changes in the sensor's power-saving state and are only
triggered upon actual state transitions. Since AC coupling uses separate
bits for activity and inactivity, each can be configured independently.
For instance, ACTIVITY can be linked with INACTIVITY_AC.

If one of the linked events is disabled, the link bit is cleared. In
that case, the remaining event will no longer reflect a state transition
but will instead trigger based on periodic inactivity or whenever the
activity threshold is exceeded.

Signed-off-by: Lothar Rubusch <l.rubusch@gmail.com>
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Link: https://patch.msgid.link/20250702230819.19353-8-l.rubusch@gmail.com
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>

authored by

Lothar Rubusch and committed by
Jonathan Cameron
56d080b9 554396d4

+200 -14
+200 -14
drivers/iio/accel/adxl313_core.c
··· 30 30 #define ADXL313_ACT_XYZ_EN GENMASK(6, 4) 31 31 #define ADXL313_INACT_XYZ_EN GENMASK(2, 0) 32 32 33 + #define ADXL313_REG_ACT_ACDC_MSK BIT(7) 34 + #define ADXL313_REG_INACT_ACDC_MSK BIT(3) 35 + #define ADXL313_COUPLING_DC 0 36 + #define ADXL313_COUPLING_AC 1 37 + 33 38 /* activity/inactivity */ 34 39 enum adxl313_activity_type { 35 40 ADXL313_ACTIVITY, 36 41 ADXL313_INACTIVITY, 42 + ADXL313_ACTIVITY_AC, 43 + ADXL313_INACTIVITY_AC, 37 44 }; 38 45 39 46 static const unsigned int adxl313_act_int_reg[] = { 40 47 [ADXL313_ACTIVITY] = ADXL313_INT_ACTIVITY, 41 48 [ADXL313_INACTIVITY] = ADXL313_INT_INACTIVITY, 49 + [ADXL313_ACTIVITY_AC] = ADXL313_INT_ACTIVITY, 50 + [ADXL313_INACTIVITY_AC] = ADXL313_INT_INACTIVITY, 42 51 }; 43 52 44 53 static const unsigned int adxl313_act_thresh_reg[] = { 45 54 [ADXL313_ACTIVITY] = ADXL313_REG_THRESH_ACT, 46 55 [ADXL313_INACTIVITY] = ADXL313_REG_THRESH_INACT, 56 + [ADXL313_ACTIVITY_AC] = ADXL313_REG_THRESH_ACT, 57 + [ADXL313_INACTIVITY_AC] = ADXL313_REG_THRESH_INACT, 58 + }; 59 + 60 + static const unsigned int adxl313_act_acdc_msk[] = { 61 + [ADXL313_ACTIVITY] = ADXL313_REG_ACT_ACDC_MSK, 62 + [ADXL313_INACTIVITY] = ADXL313_REG_INACT_ACDC_MSK, 63 + [ADXL313_ACTIVITY_AC] = ADXL313_REG_ACT_ACDC_MSK, 64 + [ADXL313_INACTIVITY_AC] = ADXL313_REG_INACT_ACDC_MSK, 47 65 }; 48 66 49 67 static const struct regmap_range adxl312_readable_reg_range[] = { ··· 273 255 .mask_separate = BIT(IIO_EV_INFO_ENABLE), 274 256 .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), 275 257 }, 258 + { 259 + /* activity, AC bit set */ 260 + .type = IIO_EV_TYPE_MAG_ADAPTIVE, 261 + .dir = IIO_EV_DIR_RISING, 262 + .mask_separate = BIT(IIO_EV_INFO_ENABLE), 263 + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE), 264 + }, 276 265 }; 277 266 278 267 static const struct iio_event_spec adxl313_inactivity_events[] = { 279 268 { 280 269 /* inactivity */ 281 270 .type = IIO_EV_TYPE_MAG, 271 + .dir = IIO_EV_DIR_FALLING, 272 + .mask_separate = BIT(IIO_EV_INFO_ENABLE), 273 + .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | 274 + BIT(IIO_EV_INFO_PERIOD), 275 + }, 276 + { 277 + /* inactivity, AC bit set */ 278 + .type = IIO_EV_TYPE_MAG_ADAPTIVE, 282 279 .dir = IIO_EV_DIR_FALLING, 283 280 .mask_separate = BIT(IIO_EV_INFO_ENABLE), 284 281 .mask_shared_by_type = BIT(IIO_EV_INFO_VALUE) | ··· 396 363 return regmap_write(data->regmap, ADXL313_REG_TIME_INACT, val); 397 364 } 398 365 366 + /** 367 + * adxl313_is_act_inact_ac() - Check if AC coupling is enabled. 368 + * @data: The device data. 369 + * @type: The activity or inactivity type. 370 + * 371 + * Provide a type of activity or inactivity, combined with either AC coupling 372 + * set, or default to DC coupling. This function verifies if the combination is 373 + * currently enabled or not. 374 + * 375 + * Return: if the provided activity type has AC coupling enabled or a negative 376 + * error value. 377 + */ 378 + static int adxl313_is_act_inact_ac(struct adxl313_data *data, 379 + enum adxl313_activity_type type) 380 + { 381 + unsigned int regval; 382 + bool coupling; 383 + int ret; 384 + 385 + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval); 386 + if (ret) 387 + return ret; 388 + 389 + coupling = adxl313_act_acdc_msk[type] & regval; 390 + 391 + switch (type) { 392 + case ADXL313_ACTIVITY: 393 + case ADXL313_INACTIVITY: 394 + return coupling == ADXL313_COUPLING_DC; 395 + case ADXL313_ACTIVITY_AC: 396 + case ADXL313_INACTIVITY_AC: 397 + return coupling == ADXL313_COUPLING_AC; 398 + default: 399 + return -EINVAL; 400 + } 401 + } 402 + 403 + static int adxl313_set_act_inact_ac(struct adxl313_data *data, 404 + enum adxl313_activity_type type, 405 + bool cmd_en) 406 + { 407 + unsigned int act_inact_ac; 408 + 409 + switch (type) { 410 + case ADXL313_ACTIVITY_AC: 411 + case ADXL313_INACTIVITY_AC: 412 + act_inact_ac = ADXL313_COUPLING_AC && cmd_en; 413 + break; 414 + case ADXL313_ACTIVITY: 415 + case ADXL313_INACTIVITY: 416 + act_inact_ac = ADXL313_COUPLING_DC && cmd_en; 417 + break; 418 + default: 419 + return -EINVAL; 420 + } 421 + 422 + return regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL, 423 + adxl313_act_acdc_msk[type], act_inact_ac); 424 + } 425 + 399 426 static int adxl313_is_act_inact_en(struct adxl313_data *data, 400 427 enum adxl313_activity_type type) 401 428 { 402 429 unsigned int axis_ctrl; 403 430 unsigned int regval; 431 + bool int_en; 404 432 int ret; 405 433 406 434 ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &axis_ctrl); ··· 471 377 /* Check if axis for activity are enabled */ 472 378 switch (type) { 473 379 case ADXL313_ACTIVITY: 380 + case ADXL313_ACTIVITY_AC: 474 381 if (!FIELD_GET(ADXL313_ACT_XYZ_EN, axis_ctrl)) 475 382 return false; 476 383 break; 477 384 case ADXL313_INACTIVITY: 385 + case ADXL313_INACTIVITY_AC: 478 386 if (!FIELD_GET(ADXL313_INACT_XYZ_EN, axis_ctrl)) 479 387 return false; 480 388 break; ··· 489 393 if (ret) 490 394 return ret; 491 395 492 - return adxl313_act_int_reg[type] & regval; 396 + int_en = adxl313_act_int_reg[type] & regval; 397 + if (!int_en) 398 + return false; 399 + 400 + /* Check if configured coupling matches provided type */ 401 + return adxl313_is_act_inact_ac(data, type); 493 402 } 494 403 495 404 static int adxl313_set_act_inact_linkbit(struct adxl313_data *data, bool en) 496 405 { 406 + int act_ac_en, inact_ac_en; 497 407 int act_en, inact_en; 498 408 499 409 act_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY); 500 410 if (act_en < 0) 501 411 return act_en; 502 412 413 + act_ac_en = adxl313_is_act_inact_en(data, ADXL313_ACTIVITY_AC); 414 + if (act_ac_en < 0) 415 + return act_ac_en; 416 + 503 417 inact_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY); 504 418 if (inact_en < 0) 505 419 return inact_en; 420 + 421 + inact_ac_en = adxl313_is_act_inact_en(data, ADXL313_INACTIVITY_AC); 422 + if (inact_ac_en < 0) 423 + return inact_ac_en; 424 + 425 + act_en = act_en || act_ac_en; 426 + 427 + inact_en = inact_en || inact_ac_en; 506 428 507 429 return regmap_assign_bits(data->regmap, ADXL313_REG_POWER_CTL, 508 430 ADXL313_POWER_CTL_AUTO_SLEEP | ADXL313_POWER_CTL_LINK, ··· 547 433 return 0; 548 434 549 435 /* When turning on inactivity, check if inact time is valid */ 550 - if (type == ADXL313_INACTIVITY) { 436 + if (type == ADXL313_INACTIVITY || type == ADXL313_INACTIVITY_AC) { 551 437 ret = regmap_read(data->regmap, 552 438 ADXL313_REG_TIME_INACT, 553 439 &inact_time_s); ··· 557 443 if (!inact_time_s) 558 444 return 0; 559 445 } 446 + } else { 447 + /* 448 + * When turning off an activity, ensure that the correct 449 + * coupling event is specified. This step helps prevent misuse - 450 + * for example, if an AC-coupled activity is active and the 451 + * current call attempts to turn off a DC-coupled activity, this 452 + * inconsistency should be detected here. 453 + */ 454 + if (adxl313_is_act_inact_ac(data, type) <= 0) 455 + return 0; 560 456 } 561 457 562 458 /* Start modifying configuration registers */ ··· 577 453 /* Enable axis according to the command */ 578 454 switch (type) { 579 455 case ADXL313_ACTIVITY: 456 + case ADXL313_ACTIVITY_AC: 580 457 axis_ctrl = ADXL313_ACT_XYZ_EN; 581 458 break; 582 459 case ADXL313_INACTIVITY: 460 + case ADXL313_INACTIVITY_AC: 583 461 axis_ctrl = ADXL313_INACT_XYZ_EN; 584 462 break; 585 463 default: ··· 589 463 } 590 464 ret = regmap_assign_bits(data->regmap, ADXL313_REG_ACT_INACT_CTL, 591 465 axis_ctrl, cmd_en); 466 + if (ret) 467 + return ret; 468 + 469 + /* Update AC/DC-coupling according to the command */ 470 + ret = adxl313_set_act_inact_ac(data, type, cmd_en); 592 471 if (ret) 593 472 return ret; 594 473 ··· 727 596 return adxl313_read_mag_config(data, dir, 728 597 ADXL313_ACTIVITY, 729 598 ADXL313_INACTIVITY); 599 + case IIO_EV_TYPE_MAG_ADAPTIVE: 600 + return adxl313_read_mag_config(data, dir, 601 + ADXL313_ACTIVITY_AC, 602 + ADXL313_INACTIVITY_AC); 730 603 default: 731 604 return -EINVAL; 732 605 } ··· 749 614 return adxl313_write_mag_config(data, dir, 750 615 ADXL313_ACTIVITY, 751 616 ADXL313_INACTIVITY, 617 + state); 618 + case IIO_EV_TYPE_MAG_ADAPTIVE: 619 + return adxl313_write_mag_config(data, dir, 620 + ADXL313_ACTIVITY_AC, 621 + ADXL313_INACTIVITY_AC, 752 622 state); 753 623 default: 754 624 return -EINVAL; ··· 854 714 ADXL313_ACTIVITY, 855 715 ADXL313_INACTIVITY, 856 716 val, val2); 717 + case IIO_EV_TYPE_MAG_ADAPTIVE: 718 + return adxl313_read_mag_value(data, dir, info, 719 + ADXL313_ACTIVITY_AC, 720 + ADXL313_INACTIVITY_AC, 721 + val, val2); 857 722 default: 858 723 return -EINVAL; 859 724 } ··· 878 733 return adxl313_write_mag_value(data, dir, info, 879 734 ADXL313_ACTIVITY, 880 735 ADXL313_INACTIVITY, 736 + val, val2); 737 + case IIO_EV_TYPE_MAG_ADAPTIVE: 738 + return adxl313_write_mag_value(data, dir, info, 739 + ADXL313_ACTIVITY_AC, 740 + ADXL313_INACTIVITY_AC, 881 741 val, val2); 882 742 default: 883 743 return -EINVAL; ··· 1030 880 static int adxl313_push_events(struct iio_dev *indio_dev, int int_stat) 1031 881 { 1032 882 s64 ts = iio_get_time_ns(indio_dev); 883 + struct adxl313_data *data = iio_priv(indio_dev); 884 + unsigned int regval; 1033 885 int ret = -ENOENT; 1034 886 1035 887 if (FIELD_GET(ADXL313_INT_ACTIVITY, int_stat)) { 1036 - ret = iio_push_event(indio_dev, 1037 - IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, 1038 - IIO_MOD_X_OR_Y_OR_Z, 1039 - IIO_EV_TYPE_MAG, 1040 - IIO_EV_DIR_RISING), 1041 - ts); 888 + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval); 1042 889 if (ret) 1043 890 return ret; 891 + 892 + if (FIELD_GET(ADXL313_REG_ACT_ACDC_MSK, regval)) { 893 + /* AC coupled */ 894 + ret = iio_push_event(indio_dev, 895 + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, 896 + IIO_MOD_X_OR_Y_OR_Z, 897 + IIO_EV_TYPE_MAG_ADAPTIVE, 898 + IIO_EV_DIR_RISING), 899 + ts); 900 + if (ret) 901 + return ret; 902 + } else { 903 + /* DC coupled, relying on THRESH */ 904 + ret = iio_push_event(indio_dev, 905 + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, 906 + IIO_MOD_X_OR_Y_OR_Z, 907 + IIO_EV_TYPE_MAG, 908 + IIO_EV_DIR_RISING), 909 + ts); 910 + if (ret) 911 + return ret; 912 + } 1044 913 } 1045 914 1046 915 if (FIELD_GET(ADXL313_INT_INACTIVITY, int_stat)) { 1047 - ret = iio_push_event(indio_dev, 1048 - IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, 1049 - IIO_MOD_X_AND_Y_AND_Z, 1050 - IIO_EV_TYPE_MAG, 1051 - IIO_EV_DIR_FALLING), 1052 - ts); 916 + ret = regmap_read(data->regmap, ADXL313_REG_ACT_INACT_CTL, &regval); 1053 917 if (ret) 1054 918 return ret; 919 + 920 + if (FIELD_GET(ADXL313_REG_INACT_ACDC_MSK, regval)) { 921 + /* AC coupled */ 922 + ret = iio_push_event(indio_dev, 923 + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, 924 + IIO_MOD_X_AND_Y_AND_Z, 925 + IIO_EV_TYPE_MAG_ADAPTIVE, 926 + IIO_EV_DIR_FALLING), 927 + ts); 928 + if (ret) 929 + return ret; 930 + } else { 931 + /* DC coupled, relying on THRESH */ 932 + ret = iio_push_event(indio_dev, 933 + IIO_MOD_EVENT_CODE(IIO_ACCEL, 0, 934 + IIO_MOD_X_AND_Y_AND_Z, 935 + IIO_EV_TYPE_MAG, 936 + IIO_EV_DIR_FALLING), 937 + ts); 938 + if (ret) 939 + return ret; 940 + } 1055 941 } 1056 942 1057 943 return ret;