302 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/****************************************************************
 | 
						|
 | 
						|
 Siano Mobile Silicon, Inc.
 | 
						|
 MDTV receiver kernel modules.
 | 
						|
 Copyright (C) 2006-2009, Uri Shkolnik
 | 
						|
 | 
						|
 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 program is distributed in the hope that it will be useful,
 | 
						|
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
						|
 GNU General Public License for more details.
 | 
						|
 | 
						|
 You should have received a copy of the GNU General Public License
 | 
						|
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
 ****************************************************************/
 | 
						|
 | 
						|
 | 
						|
#include <linux/types.h>
 | 
						|
#include <linux/input.h>
 | 
						|
 | 
						|
#include "smscoreapi.h"
 | 
						|
#include "smsir.h"
 | 
						|
#include "sms-cards.h"
 | 
						|
 | 
						|
/* In order to add new IR remote control -
 | 
						|
 * 1) Add it to the <enum ir_kb_type> @ smsir,h,
 | 
						|
 * 2) Add its map to keyboard_layout_maps below
 | 
						|
 * 3) Set your board (sms-cards sub-module) to use it
 | 
						|
 */
 | 
						|
 | 
						|
static struct keyboard_layout_map_t keyboard_layout_maps[] = {
 | 
						|
		[SMS_IR_KB_DEFAULT_TV] = {
 | 
						|
			.ir_protocol = IR_RC5,
 | 
						|
			.rc5_kbd_address = KEYBOARD_ADDRESS_TV1,
 | 
						|
			.keyboard_layout_map = {
 | 
						|
					KEY_0, KEY_1, KEY_2,
 | 
						|
					KEY_3, KEY_4, KEY_5,
 | 
						|
					KEY_6, KEY_7, KEY_8,
 | 
						|
					KEY_9, 0, 0, KEY_POWER,
 | 
						|
					KEY_MUTE, 0, 0,
 | 
						|
					KEY_VOLUMEUP, KEY_VOLUMEDOWN,
 | 
						|
					KEY_BRIGHTNESSUP,
 | 
						|
					KEY_BRIGHTNESSDOWN, KEY_CHANNELUP,
 | 
						|
					KEY_CHANNELDOWN,
 | 
						|
					0, 0, 0, 0, 0, 0, 0, 0,
 | 
						|
					0, 0, 0, 0, 0, 0, 0, 0,
 | 
						|
					0, 0, 0, 0, 0, 0, 0, 0,
 | 
						|
					0, 0, 0, 0, 0, 0, 0, 0,
 | 
						|
					0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 | 
						|
			}
 | 
						|
		},
 | 
						|
		[SMS_IR_KB_HCW_SILVER] = {
 | 
						|
			.ir_protocol = IR_RC5,
 | 
						|
			.rc5_kbd_address = KEYBOARD_ADDRESS_LIGHTING1,
 | 
						|
			.keyboard_layout_map = {
 | 
						|
					KEY_0, KEY_1, KEY_2,
 | 
						|
					KEY_3, KEY_4, KEY_5,
 | 
						|
					KEY_6, KEY_7, KEY_8,
 | 
						|
					KEY_9, KEY_TEXT, KEY_RED,
 | 
						|
					KEY_RADIO, KEY_MENU,
 | 
						|
					KEY_SUBTITLE,
 | 
						|
					KEY_MUTE, KEY_VOLUMEUP,
 | 
						|
					KEY_VOLUMEDOWN, KEY_PREVIOUS, 0,
 | 
						|
					KEY_UP, KEY_DOWN, KEY_LEFT,
 | 
						|
					KEY_RIGHT, KEY_VIDEO, KEY_AUDIO,
 | 
						|
					KEY_MHP, KEY_EPG, KEY_TV,
 | 
						|
					0, KEY_NEXTSONG, KEY_EXIT,
 | 
						|
					KEY_CHANNELUP, 	KEY_CHANNELDOWN,
 | 
						|
					KEY_CHANNEL, 0,
 | 
						|
					KEY_PREVIOUSSONG, KEY_ENTER,
 | 
						|
					KEY_SLEEP, 0, 0, KEY_BLUE,
 | 
						|
					0, 0, 0, 0, KEY_GREEN, 0,
 | 
						|
					KEY_PAUSE, 0, KEY_REWIND,
 | 
						|
					0, KEY_FASTFORWARD, KEY_PLAY,
 | 
						|
					KEY_STOP, KEY_RECORD,
 | 
						|
					KEY_YELLOW, 0, 0, KEY_SELECT,
 | 
						|
					KEY_ZOOM, KEY_POWER, 0, 0
 | 
						|
			}
 | 
						|
		},
 | 
						|
		{ } /* Terminating entry */
 | 
						|
};
 | 
						|
 | 
						|
u32 ir_pos;
 | 
						|
u32	ir_word;
 | 
						|
u32 ir_toggle;
 | 
						|
 | 
						|
#define RC5_PUSH_BIT(dst, bit, pos)	\
 | 
						|
	{ dst <<= 1; dst |= bit; pos++; }
 | 
						|
 | 
						|
 | 
						|
static void sms_ir_rc5_event(struct smscore_device_t *coredev,
 | 
						|
				u32 toggle, u32 addr, u32 cmd)
 | 
						|
{
 | 
						|
	bool toggle_changed;
 | 
						|
	u16 keycode;
 | 
						|
 | 
						|
	sms_log("IR RC5 word: address %d, command %d, toggle %d",
 | 
						|
				addr, cmd, toggle);
 | 
						|
 | 
						|
	toggle_changed = ir_toggle != toggle;
 | 
						|
	/* keep toggle */
 | 
						|
	ir_toggle = toggle;
 | 
						|
 | 
						|
	if (addr !=
 | 
						|
		keyboard_layout_maps[coredev->ir.ir_kb_type].rc5_kbd_address)
 | 
						|
		return; /* Check for valid address */
 | 
						|
 | 
						|
	keycode =
 | 
						|
		keyboard_layout_maps
 | 
						|
		[coredev->ir.ir_kb_type].keyboard_layout_map[cmd];
 | 
						|
 | 
						|
	if (!toggle_changed &&
 | 
						|
			(keycode != KEY_VOLUMEUP && keycode != KEY_VOLUMEDOWN))
 | 
						|
		return; /* accept only repeated volume, reject other keys */
 | 
						|
 | 
						|
	sms_log("kernel input keycode (from ir) %d", keycode);
 | 
						|
	input_report_key(coredev->ir.input_dev, keycode, 1);
 | 
						|
	input_sync(coredev->ir.input_dev);
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
/* decode raw bit pattern to RC5 code */
 | 
						|
/* taken from ir-functions.c */
 | 
						|
static u32 ir_rc5_decode(unsigned int code)
 | 
						|
{
 | 
						|
/*	unsigned int org_code = code;*/
 | 
						|
	unsigned int pair;
 | 
						|
	unsigned int rc5 = 0;
 | 
						|
	int i;
 | 
						|
 | 
						|
	for (i = 0; i < 14; ++i) {
 | 
						|
		pair = code & 0x3;
 | 
						|
		code >>= 2;
 | 
						|
 | 
						|
		rc5 <<= 1;
 | 
						|
		switch (pair) {
 | 
						|
		case 0:
 | 
						|
		case 2:
 | 
						|
			break;
 | 
						|
		case 1:
 | 
						|
			rc5 |= 1;
 | 
						|
			break;
 | 
						|
		case 3:
 | 
						|
/*	dprintk(1, "ir-common: ir_rc5_decode(%x) bad code\n", org_code);*/
 | 
						|
			sms_log("bad code");
 | 
						|
			return 0;
 | 
						|
		}
 | 
						|
	}
 | 
						|
/*
 | 
						|
	dprintk(1, "ir-common: code=%x, rc5=%x, start=%x,
 | 
						|
		toggle=%x, address=%x, "
 | 
						|
		"instr=%x\n", rc5, org_code, RC5_START(rc5),
 | 
						|
		RC5_TOGGLE(rc5), RC5_ADDR(rc5), RC5_INSTR(rc5));
 | 
						|
*/
 | 
						|
	return rc5;
 | 
						|
}
 | 
						|
 | 
						|
static void sms_rc5_parse_word(struct smscore_device_t *coredev)
 | 
						|
{
 | 
						|
	#define RC5_START(x)    (((x)>>12)&3)
 | 
						|
	#define RC5_TOGGLE(x)   (((x)>>11)&1)
 | 
						|
	#define RC5_ADDR(x)     (((x)>>6)&0x1F)
 | 
						|
	#define RC5_INSTR(x)    ((x)&0x3F)
 | 
						|
 | 
						|
	int i, j;
 | 
						|
	u32 rc5_word = 0;
 | 
						|
 | 
						|
	/* Reverse the IR word direction */
 | 
						|
	for (i = 0 ; i < 28 ; i++)
 | 
						|
		RC5_PUSH_BIT(rc5_word, (ir_word>>i)&1, j)
 | 
						|
 | 
						|
	rc5_word = ir_rc5_decode(rc5_word);
 | 
						|
	/* sms_log("temp = 0x%x, rc5_code = 0x%x", ir_word, rc5_word); */
 | 
						|
 | 
						|
	sms_ir_rc5_event(coredev,
 | 
						|
				RC5_TOGGLE(rc5_word),
 | 
						|
				RC5_ADDR(rc5_word),
 | 
						|
				RC5_INSTR(rc5_word));
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void sms_rc5_accumulate_bits(struct smscore_device_t *coredev,
 | 
						|
		s32 ir_sample)
 | 
						|
{
 | 
						|
	#define RC5_TIME_GRANULARITY	200
 | 
						|
	#define RC5_DEF_BIT_TIME		889
 | 
						|
	#define RC5_MAX_SAME_BIT_CONT	4
 | 
						|
	#define RC5_WORD_LEN			27 /* 28 bit */
 | 
						|
 | 
						|
	u32 i, j;
 | 
						|
	s32 delta_time;
 | 
						|
	u32 time = (ir_sample > 0) ? ir_sample : (0-ir_sample);
 | 
						|
	u32 level = (ir_sample < 0) ? 0 : 1;
 | 
						|
 | 
						|
	for (i = RC5_MAX_SAME_BIT_CONT; i > 0; i--) {
 | 
						|
		delta_time = time - (i*RC5_DEF_BIT_TIME) + RC5_TIME_GRANULARITY;
 | 
						|
		if (delta_time < 0)
 | 
						|
			continue; /* not so many consecutive bits */
 | 
						|
		if (delta_time > (2 * RC5_TIME_GRANULARITY)) {
 | 
						|
			/* timeout */
 | 
						|
			if (ir_pos == (RC5_WORD_LEN-1))
 | 
						|
				/* complete last bit */
 | 
						|
				RC5_PUSH_BIT(ir_word, level, ir_pos)
 | 
						|
 | 
						|
			if (ir_pos == RC5_WORD_LEN)
 | 
						|
				sms_rc5_parse_word(coredev);
 | 
						|
			else if (ir_pos) /* timeout within a word */
 | 
						|
				sms_log("IR error parsing a word");
 | 
						|
 | 
						|
			ir_pos = 0;
 | 
						|
			ir_word = 0;
 | 
						|
			/* sms_log("timeout %d", time); */
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		/* The time is within the range of this number of bits */
 | 
						|
		for (j = 0 ; j < i ; j++)
 | 
						|
			RC5_PUSH_BIT(ir_word, level, ir_pos)
 | 
						|
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void sms_ir_event(struct smscore_device_t *coredev, const char *buf, int len)
 | 
						|
{
 | 
						|
	#define IR_DATA_RECEIVE_MAX_LEN	520 /* 128*4 + 4 + 4 */
 | 
						|
	u32 i;
 | 
						|
	enum ir_protocol ir_protocol =
 | 
						|
			keyboard_layout_maps[coredev->ir.ir_kb_type]
 | 
						|
					     .ir_protocol;
 | 
						|
	s32 *samples;
 | 
						|
	int count = len>>2;
 | 
						|
 | 
						|
	samples = (s32 *)buf;
 | 
						|
/*	sms_log("IR buffer received, length = %d", count);*/
 | 
						|
 | 
						|
	for (i = 0; i < count; i++)
 | 
						|
		if (ir_protocol == IR_RC5)
 | 
						|
			sms_rc5_accumulate_bits(coredev, samples[i]);
 | 
						|
	/*  IR_RCMM not implemented */
 | 
						|
}
 | 
						|
 | 
						|
int sms_ir_init(struct smscore_device_t *coredev)
 | 
						|
{
 | 
						|
	struct input_dev *input_dev;
 | 
						|
 | 
						|
	sms_log("Allocating input device");
 | 
						|
	input_dev = input_allocate_device();
 | 
						|
	if (!input_dev)	{
 | 
						|
		sms_err("Not enough memory");
 | 
						|
		return -ENOMEM;
 | 
						|
	}
 | 
						|
 | 
						|
	coredev->ir.input_dev = input_dev;
 | 
						|
	coredev->ir.ir_kb_type =
 | 
						|
		sms_get_board(smscore_get_board_id(coredev))->ir_kb_type;
 | 
						|
	coredev->ir.keyboard_layout_map =
 | 
						|
		keyboard_layout_maps[coredev->ir.ir_kb_type].
 | 
						|
				keyboard_layout_map;
 | 
						|
	sms_log("IR remote keyboard type is %d", coredev->ir.ir_kb_type);
 | 
						|
 | 
						|
	coredev->ir.controller = 0;	/* Todo: vega/nova SPI number */
 | 
						|
	coredev->ir.timeout = IR_DEFAULT_TIMEOUT;
 | 
						|
	sms_log("IR port %d, timeout %d ms",
 | 
						|
			coredev->ir.controller, coredev->ir.timeout);
 | 
						|
 | 
						|
	snprintf(coredev->ir.name,
 | 
						|
				IR_DEV_NAME_MAX_LEN,
 | 
						|
				"SMS IR w/kbd type %d",
 | 
						|
				coredev->ir.ir_kb_type);
 | 
						|
	input_dev->name = coredev->ir.name;
 | 
						|
	input_dev->phys = coredev->ir.name;
 | 
						|
	input_dev->dev.parent = coredev->device;
 | 
						|
 | 
						|
	/* Key press events only */
 | 
						|
	input_dev->evbit[0] = BIT_MASK(EV_KEY);
 | 
						|
	input_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
 | 
						|
 | 
						|
	sms_log("Input device (IR) %s is set for key events", input_dev->name);
 | 
						|
 | 
						|
	if (input_register_device(input_dev)) {
 | 
						|
		sms_err("Failed to register device");
 | 
						|
		input_free_device(input_dev);
 | 
						|
		return -EACCES;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
void sms_ir_exit(struct smscore_device_t *coredev)
 | 
						|
{
 | 
						|
	if (coredev->ir.input_dev)
 | 
						|
		input_unregister_device(coredev->ir.input_dev);
 | 
						|
 | 
						|
	sms_log("");
 | 
						|
}
 | 
						|
 |