// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2021 Samsung Electronics Co., Ltd. * http://www.samsung.com * Author: Marek Szyprowski */ #include #include #include #include #include #include #include #include /** * struct button_adc_priv - private data for button-adc driver. * * @adc: Analog to Digital Converter device to which button is connected. * @channel: channel of the ADC device to probe the button state. * @min: minimal uV value to consider button as pressed. * @max: maximal uV value to consider button as pressed. */ struct button_adc_priv { struct udevice *adc; int channel; int min; int max; }; static enum button_state_t button_adc_get_state(struct udevice *dev) { struct button_adc_priv *priv = dev_get_priv(dev); unsigned int val; int ret, uV; ret = adc_start_channel(priv->adc, priv->channel); if (ret) return ret; ret = adc_channel_data(priv->adc, priv->channel, &val); if (ret) return ret; ret = adc_raw_to_uV(priv->adc, val, &uV); if (ret) return ret; return (uV >= priv->min && uV < priv->max) ? BUTTON_ON : BUTTON_OFF; } static int button_adc_of_to_plat(struct udevice *dev) { struct button_uc_plat *uc_plat = dev_get_uclass_plat(dev); struct button_adc_priv *priv = dev_get_priv(dev); struct ofnode_phandle_args args; u32 down_threshold = 0, up_threshold, voltage, t; ofnode node; int ret; /* Ignore the top-level button node */ if (!uc_plat->label) return 0; ret = dev_read_phandle_with_args(dev->parent, "io-channels", "#io-channel-cells", 0, 0, &args); if (ret) return ret; ret = uclass_get_device_by_ofnode(UCLASS_ADC, args.node, &priv->adc); if (ret) return ret; ret = ofnode_read_u32(dev_ofnode(dev->parent), "keyup-threshold-microvolt", &up_threshold); if (ret) return ret; ret = ofnode_read_u32(dev_ofnode(dev), "press-threshold-microvolt", &voltage); if (ret) return ret; dev_for_each_subnode(node, dev->parent) { ret = ofnode_read_u32(node, "press-threshold-microvolt", &t); if (ret) return ret; if (t > voltage && t < up_threshold) up_threshold = t; else if (t < voltage && t > down_threshold) down_threshold = t; } priv->channel = args.args[0]; /* * Define the voltage range such that the button is only pressed * when the voltage is closest to its own press-threshold-microvolt */ if (down_threshold == 0) priv->min = 0; else priv->min = down_threshold + (voltage - down_threshold) / 2; priv->max = voltage + (up_threshold - voltage) / 2; return ret; } static int button_adc_bind(struct udevice *parent) { struct udevice *dev; ofnode node; int ret; dev_for_each_subnode(node, parent) { struct button_uc_plat *uc_plat; const char *label; label = ofnode_read_string(node, "label"); if (!label) { debug("%s: node %s has no label\n", __func__, ofnode_get_name(node)); return -EINVAL; } ret = device_bind_driver_to_node(parent, "button_adc", ofnode_get_name(node), node, &dev); if (ret) return ret; uc_plat = dev_get_uclass_plat(dev); uc_plat->label = label; } return 0; } static const struct button_ops button_adc_ops = { .get_state = button_adc_get_state, }; static const struct udevice_id button_adc_ids[] = { { .compatible = "adc-keys" }, { } }; U_BOOT_DRIVER(button_adc) = { .name = "button_adc", .id = UCLASS_BUTTON, .of_match = button_adc_ids, .ops = &button_adc_ops, .priv_auto = sizeof(struct button_adc_priv), .bind = button_adc_bind, .of_to_plat = button_adc_of_to_plat, };