summaryrefslogtreecommitdiff
path: root/drivers/input/cpcap_pwrbutton.c
blob: c8ad39d33ca990ec61efa08599eae48150a27f11 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// SPDX-License-Identifier: GPL-2.0+
/*
 *  Copyright(C) 2025 Svyatoslav Ryhel <clamor95@gmail.com>
 */

#include <stdlib.h>
#include <dm.h>
#include <input.h>
#include <keyboard.h>
#include <power/pmic.h>
#include <power/cpcap.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/input.h>

static const unsigned int cpcpap_to_reg[] = {
	CPCAP_REG_INT1,
	CPCAP_REG_INT2,
	CPCAP_REG_INT3,
	CPCAP_REG_INT4,
};

/**
 * struct cpcap_pwrbutton_priv
 *
 * @bank: id of interrupt bank co-responding to an IRQ register
 * @id: id of interrupt pin co-responding to the bit in IRQ register
 * @keycode: linux key code
 * @old_state: holder of last button state
 * @skip: holder of keycode skip state. This is required since both pressing
 *        and releasing generate same event and cause key send duplication.
 */
struct cpcap_pwrbutton_priv {
	u32 bank;
	u32 id;

	u32 keycode;

	bool old_state;
	bool skip;
};

static int cpcap_pwrbutton_read_keys(struct input_config *input)
{
	struct udevice *dev = input->dev;
	struct cpcap_pwrbutton_priv *priv = dev_get_priv(dev);
	u32 value, state_changed;
	bool state;

	value = pmic_reg_read(dev->parent, cpcpap_to_reg[priv->bank]) &
			      BIT(priv->id);

	/* Interrupt bit is cleared by writing it to interrupt reg */
	pmic_reg_write(dev->parent, cpcpap_to_reg[priv->bank], BIT(priv->id));

	state = value >> priv->id;
	state_changed = state != priv->old_state;

	if (state_changed && !priv->skip) {
		priv->old_state = state;
		input_add_keycode(input, priv->keycode, state);
	}

	if (state)
		priv->skip = !priv->skip;

	return 0;
}

static int cpcap_pwrbutton_of_to_plat(struct udevice *dev)
{
	struct cpcap_pwrbutton_priv *priv = dev_get_priv(dev);
	ofnode irq_parent;
	u32 irq_desc;
	int ret;

	/* Check interrupt parent, driver supports only CPCAP as parent */
	irq_parent = ofnode_parse_phandle(dev_ofnode(dev), "interrupt-parent", 0);
	if (!ofnode_device_is_compatible(irq_parent, "motorola,cpcap"))
		return -EINVAL;

	ret = dev_read_u32(dev, "interrupts", &irq_desc);
	if (ret)
		return ret;

	/* IRQ registers are 16 bit wide */
	priv->bank = irq_desc / 16;
	priv->id = irq_desc % 16;

	ret = dev_read_u32(dev, "linux,code", &priv->keycode);
	if (ret)
		return ret;

	priv->old_state = false;
	priv->skip = false;
	return 0;
}

static int cpcap_pwrbutton_probe(struct udevice *dev)
{
	struct keyboard_priv *uc_priv = dev_get_uclass_priv(dev);
	struct stdio_dev *sdev = &uc_priv->sdev;
	struct input_config *input = &uc_priv->input;
	int ret;

	input_init(input, false);
	input_add_tables(input, false);

	/* Register the device */
	input->dev = dev;
	input->read_keys = cpcap_pwrbutton_read_keys;
	strcpy(sdev->name, "cpcap-pwrbutton");
	ret = input_stdio_register(sdev);
	if (ret) {
		log_debug("%s: input_stdio_register() failed\n", __func__);
		return ret;
	}

	return 0;
}

static const struct udevice_id cpcap_pwrbutton_ids[] = {
	{ .compatible = "motorola,cpcap-pwrbutton" },
	{ }
};

U_BOOT_DRIVER(cpcap_pwrbutton) = {
	.name		= "cpcap_pwrbutton",
	.id		= UCLASS_KEYBOARD,
	.of_match	= cpcap_pwrbutton_ids,
	.of_to_plat	= cpcap_pwrbutton_of_to_plat,
	.probe		= cpcap_pwrbutton_probe,
	.priv_auto	= sizeof(struct cpcap_pwrbutton_priv),
};