summaryrefslogtreecommitdiffstats
path: root/drivers/media/rc/img-ir/img-ir-raw.c
blob: 6e545680d3b6039220bc543ba981b87d822cbbdb (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
/*
 * ImgTec IR Raw Decoder found in PowerDown Controller.
 *
 * Copyright 2010-2014 Imagination Technologies Ltd.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or (at your
 * option) any later version.
 *
 * This ties into the input subsystem using the RC-core in raw mode. Raw IR
 * signal edges are reported and decoded by generic software decoders.
 */

#include <linux/spinlock.h>
#include <media/rc-core.h>
#include "img-ir.h"

#define ECHO_TIMEOUT_MS 150	/* ms between echos */

/* must be called with priv->lock held */
static void img_ir_refresh_raw(struct img_ir_priv *priv, u32 irq_status)
{
	struct img_ir_priv_raw *raw = &priv->raw;
	struct rc_dev *rc_dev = priv->raw.rdev;
	int multiple;
	u32 ir_status;

	/* find whether both rise and fall was detected */
	multiple = ((irq_status & IMG_IR_IRQ_EDGE) == IMG_IR_IRQ_EDGE);
	/*
	 * If so, we need to see if the level has actually changed.
	 * If it's just noise that we didn't have time to process,
	 * there's no point reporting it.
	 */
	ir_status = img_ir_read(priv, IMG_IR_STATUS) & IMG_IR_IRRXD;
	if (multiple && ir_status == raw->last_status)
		return;
	raw->last_status = ir_status;

	/* report the edge to the IR raw decoders */
	if (ir_status) /* low */
		ir_raw_event_store_edge(rc_dev, false);
	else /* high */
		ir_raw_event_store_edge(rc_dev, true);
	ir_raw_event_handle(rc_dev);
}

/* called with priv->lock held */
void img_ir_isr_raw(struct img_ir_priv *priv, u32 irq_status)
{
	struct img_ir_priv_raw *raw = &priv->raw;

	/* check not removing */
	if (!raw->rdev)
		return;

	img_ir_refresh_raw(priv, irq_status);

	/* start / push back the echo timer */
	mod_timer(&raw->timer, jiffies + msecs_to_jiffies(ECHO_TIMEOUT_MS));
}

/*
 * Echo timer callback function.
 * The raw decoders expect to get a final sample even if there are no edges, in
 * order to be assured of the final space. If there are no edges for a certain
 * time we use this timer to emit a final sample to satisfy them.
 */
static void img_ir_echo_timer(struct timer_list *t)
{
	struct img_ir_priv *priv = from_timer(priv, t, raw.timer);

	spin_lock_irq(&priv->lock);

	/* check not removing */
	if (priv->raw.rdev)
		/*
		 * It's safe to pass irq_status=0 since it's only used to check
		 * for double edges.
		 */
		img_ir_refresh_raw(priv, 0);

	spin_unlock_irq(&priv->lock);
}

void img_ir_setup_raw(struct img_ir_priv *priv)
{
	u32 irq_en;

	if (!priv->raw.rdev)
		return;

	/* clear and enable edge interrupts */
	spin_lock_irq(&priv->lock);
	irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE);
	irq_en |= IMG_IR_IRQ_EDGE;
	img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE);
	img_ir_write(priv, IMG_IR_IRQ_ENABLE, irq_en);
	spin_unlock_irq(&priv->lock);
}

int img_ir_probe_raw(struct img_ir_priv *priv)
{
	struct img_ir_priv_raw *raw = &priv->raw;
	struct rc_dev *rdev;
	int error;

	/* Set up the echo timer */
	timer_setup(&raw->timer, img_ir_echo_timer, 0);

	/* Allocate raw decoder */
	raw->rdev = rdev = rc_allocate_device(RC_DRIVER_IR_RAW);
	if (!rdev) {
		dev_err(priv->dev, "cannot allocate raw input device\n");
		return -ENOMEM;
	}
	rdev->priv = priv;
	rdev->map_name = RC_MAP_EMPTY;
	rdev->device_name = "IMG Infrared Decoder Raw";

	/* Register raw decoder */
	error = rc_register_device(rdev);
	if (error) {
		dev_err(priv->dev, "failed to register raw IR input device\n");
		rc_free_device(rdev);
		raw->rdev = NULL;
		return error;
	}

	return 0;
}

void img_ir_remove_raw(struct img_ir_priv *priv)
{
	struct img_ir_priv_raw *raw = &priv->raw;
	struct rc_dev *rdev = raw->rdev;
	u32 irq_en;

	if (!rdev)
		return;

	/* switch off and disable raw (edge) interrupts */
	spin_lock_irq(&priv->lock);
	raw->rdev = NULL;
	irq_en = img_ir_read(priv, IMG_IR_IRQ_ENABLE);
	irq_en &= ~IMG_IR_IRQ_EDGE;
	img_ir_write(priv, IMG_IR_IRQ_ENABLE, irq_en);
	img_ir_write(priv, IMG_IR_IRQ_CLEAR, IMG_IR_IRQ_EDGE);
	spin_unlock_irq(&priv->lock);

	rc_unregister_device(rdev);

	del_timer_sync(&raw->timer);
}