/*
* driver for ENE KB3926 B/C/D/E/F CIR (pnp id: ENE0XXX)
*
* Copyright (C) 2010 Maxim Levitsky <maximlevitsky@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
* Special thanks to:
* Sami R. <maesesami@gmail.com> for lot of help in debugging and therefore
* bringing to life support for transmission & learning mode.
*
* Charlie Andrews <charliethepilot@googlemail.com> for lots of help in
* bringing up the support of new firmware buffer that is popular
* on latest notebooks
*
* ENE for partial device documentation
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/pnp.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <media/rc-core.h>
#include "ene_ir.h"
static int sample_period;
static bool learning_mode_force;
static int debug;
static bool txsim;
static void ene_set_reg_addr(struct ene_device *dev, u16 reg)
{
outb(reg >> 8, dev->hw_io + ENE_ADDR_HI);
outb(reg & 0xFF, dev->hw_io + ENE_ADDR_LO);
}
/* read a hardware register */
static u8 ene_read_reg(struct ene_device *dev, u16 reg)
{
u8 retval;
ene_set_reg_addr(dev, reg);
retval = inb(dev->hw_io + ENE_IO);
dbg_regs("reg %04x == %02x", reg, retval);
return retval;
}
/* write a hardware register */
static void ene_write_reg(struct ene_device *dev, u16 reg, u8 value)
{
dbg_regs("reg %04x <- %02x", reg, value);
ene_set_reg_addr(dev, reg);
outb(value, dev->hw_io + ENE_IO);
}
/* Set bits in hardware register */
static void ene_set_reg_mask(struct ene_device *dev, u16 reg, u8 mask)
{
dbg_regs("reg %04x |= %02x", reg, mask);
ene_set_reg_addr(dev, reg);
outb(inb(dev->hw_io + ENE_IO) | mask, dev->hw_io + ENE_IO);
}
/* Clear bits in hardware register */
static void ene_clear_reg_mask(struct ene_device *dev, u16 reg, u8 mask)
{
dbg_regs("reg %04x &= ~%02x ", reg, mask);
ene_set_reg_addr(dev, reg);
outb(inb(dev->hw_io + ENE_IO) & ~mask, dev->hw_io + ENE_IO);
}
/* A helper to set/clear a bit in register according to boolean variable */
static void ene_set_clear_reg_mask(struct ene_device *dev, u16 reg, u8 mask,
bool set)
{
if (set)
ene_set_reg_mask(dev, reg, mask);
else
ene_clear_reg_mask(dev, reg, mask);
}
/* detect hardware features */
static int ene_hw_detect(struct ene_device *dev)
{
u8 chip_major, chip_minor;
u8 hw_revision, old_ver;
u8 fw_reg2, fw_reg1;
ene_clear_reg_mask(dev, ENE_ECSTS, ENE_ECSTS_RSRVD);
chip_major = ene_read_reg(dev, ENE_ECVER_MAJOR);
chip_minor = ene_read_reg(dev, ENE_ECVER_MINOR);
ene_set_reg_mask(dev, ENE_ECSTS, ENE_ECSTS_RSRVD);
hw_revision = ene_read_reg(dev, ENE_ECHV);
old_ver = ene_read_reg(dev, ENE_HW_VER_OLD);
dev->pll_freq = (ene_read_reg(dev, ENE_PLLFRH) << 4) +
(ene_read_reg(dev, ENE_PLLFRL) >> 4);
if (sample_period != ENE_DEFAULT_SAMPLE_PERIOD)
dev->rx_period_adjust =
dev->pll_freq == ENE_DEFAULT_PLL_FREQ ? 2 : 4;
if (hw_revision == 0xFF) {
pr_warn("device seems to be disabled\n");
pr_warn("send a mail to lirc-list@lists.sourceforge.net\n");
pr_warn("please attach output of acpidump and dmidecode\n");
return -ENODEV;
}
pr_notice("chip is 0x%02x%02x - kbver = 0x%02x, rev = 0x%02x\n",
chip_major, chip_minor, old_ver, hw_revision);
pr_notice("PLL freq = %d\n", dev->pll_freq);
if (chip_major == 0x33) {
pr_warn("chips 0x33xx aren't supported\n");
return -ENODEV;
}
if (chip_major == 0x39 && chip_minor == 0x26 && hw_revision == 0xC0) {
dev->hw_revision = ENE_HW_C;
pr_notice("KB3926C detected\n");
} else if (old_ver == 0x24 && hw_revision == 0xC0) {
dev->hw_revision = ENE_HW_B;
pr_notice("KB3926B detected\n");
} else {
dev->hw_revision = ENE_HW_D;
pr_notice("KB3926D or higher detected\n");
}
/* detect features hardware supports */
if (dev->hw_revision < ENE_HW_C)
return 0;
fw_reg1 = ene_read_reg(dev, ENE_FW1);
fw_reg2 = ene_read_reg(dev, ENE_FW2);
pr_notice("Firmware regs: %02x %02x\n", fw_reg1, fw_reg2);
dev->hw_use_gpio_0a = !!(fw_reg2 & ENE_FW2_GP0A);
dev->hw_learning_and_tx_capable = !!(fw_reg2 & ENE_FW2_LEARNING);
dev->hw_extra_buffer = !!(fw_reg1 & ENE_FW1_HAS_EXTRA_BUF);
if (dev->hw_learning_and_tx_capable)
dev->hw_fan_input = !!(fw_reg2 & ENE_FW2_FAN_INPUT);
pr_notice("Hardware features:\n");
if (dev->hw_learning_and_tx_capable) {
|