summaryrefslogtreecommitdiffstats
path: root/drivers/media/rc/zx-irdec.c
blob: 12d322ec8a291d387299b10bd69a869165ff8718 (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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/*
 * Copyright (C) 2017 Sanechips Technology Co., Ltd.
 * Copyright 2017 Linaro Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/device.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>

#include <media/rc-core.h>

#define DRIVER_NAME		"zx-irdec"

#define ZX_IR_ENABLE		0x04
#define ZX_IREN			BIT(0)
#define ZX_IR_CTRL		0x08
#define ZX_DEGL_MASK		GENMASK(21, 20)
#define ZX_DEGL_VALUE(x)	(((x) << 20) & ZX_DEGL_MASK)
#define ZX_WDBEGIN_MASK		GENMASK(18, 8)
#define ZX_WDBEGIN_VALUE(x)	(((x) << 8) & ZX_WDBEGIN_MASK)
#define ZX_IR_INTEN		0x10
#define ZX_IR_INTSTCLR		0x14
#define ZX_IR_CODE		0x30
#define ZX_IR_CNUM		0x34
#define ZX_NECRPT		BIT(16)

struct zx_irdec {
	void __iomem *base;
	struct rc_dev *rcd;
};

static void zx_irdec_set_mask(struct zx_irdec *irdec, unsigned int reg,
			      u32 mask, u32 value)
{
	u32 data;

	data = readl(irdec->base + reg);
	data &= ~mask;
	data |= value & mask;
	writel(data, irdec->base + reg);
}

static irqreturn_t zx_irdec_irq(int irq, void *dev_id)
{
	struct zx_irdec *irdec = dev_id;
	u8 address, not_address;
	u8 command, not_command;
	u32 rawcode, scancode;
	enum rc_proto rc_proto;

	/* Clear interrupt */
	writel(1, irdec->base + ZX_IR_INTSTCLR);

	/* Check repeat frame */
	if (readl(irdec->base + ZX_IR_CNUM) & ZX_NECRPT) {
		rc_repeat(irdec->rcd);
		goto done;
	}

	rawcode = readl(irdec->base + ZX_IR_CODE);
	not_command = (rawcode >> 24) & 0xff;
	command = (rawcode >> 16) & 0xff;
	not_address = (rawcode >> 8) & 0xff;
	address = rawcode & 0xff;

	scancode = ir_nec_bytes_to_scancode(address, not_address,
					    command, not_command,
					    &rc_proto);
	rc_keydown(irdec->rcd, rc_proto, scancode, 0);

done:
	return IRQ_HANDLED;
}

static int zx_irdec_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct zx_irdec *irdec;
	struct resource *res;
	struct rc_dev *rcd;
	int irq;
	int ret;

	irdec = devm_kzalloc(dev, sizeof(*irdec), GFP_KERNEL);
	if (!irdec)
		return -ENOMEM;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	irdec->base = devm_ioremap_resource(dev, res);
	if (IS_ERR(irdec->base))
		return PTR_ERR(irdec->base);

	irq = platform_get_irq(pdev, 0);
	if (irq < 0)
		return irq;

	rcd = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE);
	if (!rcd) {
		dev_err(dev, "failed to allocate rc device\n");
		return -ENOMEM;
	}

	irdec->rcd = rcd;

	rcd->priv = irdec;
	rcd->input_phys = DRIVER_NAME "/input0";
	rcd->input_id.bustype = BUS_HOST;
	rcd->map_name = RC_MAP_ZX_IRDEC;
	rcd->allowed_protocols = RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX |
							RC_PROTO_BIT_NEC32;
	rcd->driver_name = DRIVER_NAME;
	rcd->device_name = DRIVER_NAME;

	platform_set_drvdata(pdev, irdec);

	ret = devm_rc_register_device(dev, rcd);
	if (ret) {
		dev_err(dev, "failed to register rc device\n");
		return ret;
	}

	ret = devm_request_irq(dev, irq, zx_irdec_irq, 0, NULL, irdec);
	if (ret) {
		dev_err(dev, "failed to request irq\n");
		return ret;
	}

	/*
	 * Initialize deglitch level and watchdog counter beginner as
	 * recommended by vendor BSP code.
	 */
	zx_irdec_set_mask(irdec, ZX_IR_CTRL, ZX_DEGL_MASK, ZX_DEGL_VALUE(0));
	zx_irdec_set_mask(irdec, ZX_IR_CTRL, ZX_WDBEGIN_MASK,
			  ZX_WDBEGIN_VALUE(0x21c));

	/* Enable interrupt */
	writel(1, irdec->base + ZX_IR_INTEN);

	/* Enable the decoder */
	zx_irdec_set_mask(irdec, ZX_IR_ENABLE, ZX_IREN, ZX_IREN);

	return 0;
}

static int zx_irdec_remove(struct platform_device *pdev)
{
	struct zx_irdec *irdec = platform_get_drvdata(pdev);

	/* Disable the decoder */
	zx_irdec_set_mask(irdec, ZX_IR_ENABLE, ZX_IREN, 0);

	/* Disable interrupt */
	writel(0, irdec->base + ZX_IR_INTEN);

	return 0;
}

static const struct of_device_id zx_irdec_match[] = {
	{ .compatible = "zte,zx296718-irdec" },
	{ },
};
MODULE_DEVICE_TABLE(of, zx_irdec_match);

static struct platform_driver zx_irdec_driver = {
	.probe = zx_irdec_probe,
	.remove = zx_irdec_remove,
	.driver = {
		.name = DRIVER_NAME,
		.of_match_table	= zx_irdec_match,
	},
};
module_platform_driver(zx_irdec_driver);

MODULE_DESCRIPTION("ZTE ZX IR remote control driver");
MODULE_AUTHOR("Shawn Guo <shawn.guo@linaro.org>");
MODULE_LICENSE("GPL v2");