// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 
 | 
/* 
 | 
 * CEC driver for SECO X86 Boards 
 | 
 * 
 | 
 * Author:  Ettore Chimenti <ek5.chimenti@gmail.com> 
 | 
 * Copyright (C) 2018, SECO SpA. 
 | 
 * Copyright (C) 2018, Aidilab Srl. 
 | 
 */ 
 | 
  
 | 
#include <linux/module.h> 
 | 
#include <linux/acpi.h> 
 | 
#include <linux/delay.h> 
 | 
#include <linux/dmi.h> 
 | 
#include <linux/gpio/consumer.h> 
 | 
#include <linux/gpio.h> 
 | 
#include <linux/interrupt.h> 
 | 
#include <linux/pci.h> 
 | 
#include <linux/platform_device.h> 
 | 
  
 | 
/* CEC Framework */ 
 | 
#include <media/cec-notifier.h> 
 | 
  
 | 
#include "seco-cec.h" 
 | 
  
 | 
struct secocec_data { 
 | 
    struct device *dev; 
 | 
    struct platform_device *pdev; 
 | 
    struct cec_adapter *cec_adap; 
 | 
    struct cec_notifier *notifier; 
 | 
    struct rc_dev *ir; 
 | 
    char ir_input_phys[32]; 
 | 
    int irq; 
 | 
}; 
 | 
  
 | 
#define smb_wr16(cmd, data) smb_word_op(CMD_WORD_DATA, SECOCEC_MICRO_ADDRESS, \ 
 | 
                         cmd, data, SMBUS_WRITE, NULL) 
 | 
#define smb_rd16(cmd, res) smb_word_op(CMD_WORD_DATA, SECOCEC_MICRO_ADDRESS, \ 
 | 
                       cmd, 0, SMBUS_READ, res) 
 | 
  
 | 
static int smb_word_op(short data_format, u16 slave_addr, u8 cmd, u16 data, 
 | 
               u8 operation, u16 *result) 
 | 
{ 
 | 
    unsigned int count; 
 | 
    short _data_format; 
 | 
    int status = 0; 
 | 
  
 | 
    switch (data_format) { 
 | 
    case CMD_BYTE_DATA: 
 | 
        _data_format = BRA_SMB_CMD_BYTE_DATA; 
 | 
        break; 
 | 
    case CMD_WORD_DATA: 
 | 
        _data_format = BRA_SMB_CMD_WORD_DATA; 
 | 
        break; 
 | 
    default: 
 | 
        return -EINVAL; 
 | 
    } 
 | 
  
 | 
    /* Active wait until ready */ 
 | 
    for (count = 0; count <= SMBTIMEOUT; ++count) { 
 | 
        if (!(inb(HSTS) & BRA_INUSE_STS)) 
 | 
            break; 
 | 
        udelay(SMB_POLL_UDELAY); 
 | 
    } 
 | 
  
 | 
    if (count > SMBTIMEOUT) 
 | 
        /* Reset the lock instead of failing */ 
 | 
        outb(0xff, HSTS); 
 | 
  
 | 
    outb(0x00, HCNT); 
 | 
    outb((u8)(slave_addr & 0xfe) | operation, XMIT_SLVA); 
 | 
    outb(cmd, HCMD); 
 | 
    inb(HCNT); 
 | 
  
 | 
    if (operation == SMBUS_WRITE) { 
 | 
        outb((u8)data, HDAT0); 
 | 
        outb((u8)(data >> 8), HDAT1); 
 | 
    } 
 | 
  
 | 
    outb(BRA_START + _data_format, HCNT); 
 | 
  
 | 
    for (count = 0; count <= SMBTIMEOUT; count++) { 
 | 
        if (!(inb(HSTS) & BRA_HOST_BUSY)) 
 | 
            break; 
 | 
        udelay(SMB_POLL_UDELAY); 
 | 
    } 
 | 
  
 | 
    if (count > SMBTIMEOUT) { 
 | 
        status = -EBUSY; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    if (inb(HSTS) & BRA_HSTS_ERR_MASK) { 
 | 
        status = -EIO; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    if (operation == SMBUS_READ) 
 | 
        *result = ((inb(HDAT0) & 0xff) + ((inb(HDAT1) & 0xff) << 8)); 
 | 
  
 | 
err: 
 | 
    outb(0xff, HSTS); 
 | 
    return status; 
 | 
} 
 | 
  
 | 
static int secocec_adap_enable(struct cec_adapter *adap, bool enable) 
 | 
{ 
 | 
    struct secocec_data *cec = cec_get_drvdata(adap); 
 | 
    struct device *dev = cec->dev; 
 | 
    u16 val = 0; 
 | 
    int status; 
 | 
  
 | 
    if (enable) { 
 | 
        /* Clear the status register */ 
 | 
        status = smb_rd16(SECOCEC_STATUS_REG_1, &val); 
 | 
        if (status) 
 | 
            goto err; 
 | 
  
 | 
        status = smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
        if (status) 
 | 
            goto err; 
 | 
  
 | 
        /* Enable the interrupts */ 
 | 
        status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
        if (status) 
 | 
            goto err; 
 | 
  
 | 
        status = smb_wr16(SECOCEC_ENABLE_REG_1, 
 | 
                  val | SECOCEC_ENABLE_REG_1_CEC); 
 | 
        if (status) 
 | 
            goto err; 
 | 
  
 | 
        dev_dbg(dev, "Device enabled"); 
 | 
    } else { 
 | 
        /* Clear the status register */ 
 | 
        status = smb_rd16(SECOCEC_STATUS_REG_1, &val); 
 | 
        status = smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
  
 | 
        /* Disable the interrupts */ 
 | 
        status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
        status = smb_wr16(SECOCEC_ENABLE_REG_1, val & 
 | 
                  ~SECOCEC_ENABLE_REG_1_CEC & 
 | 
                  ~SECOCEC_ENABLE_REG_1_IR); 
 | 
  
 | 
        dev_dbg(dev, "Device disabled"); 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
err: 
 | 
    return status; 
 | 
} 
 | 
  
 | 
static int secocec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr) 
 | 
{ 
 | 
    u16 enable_val = 0; 
 | 
    int status; 
 | 
  
 | 
    /* Disable device */ 
 | 
    status = smb_rd16(SECOCEC_ENABLE_REG_1, &enable_val); 
 | 
    if (status) 
 | 
        return status; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_ENABLE_REG_1, 
 | 
              enable_val & ~SECOCEC_ENABLE_REG_1_CEC); 
 | 
    if (status) 
 | 
        return status; 
 | 
  
 | 
    /* Write logical address 
 | 
     * NOTE: CEC_LOG_ADDR_INVALID is mapped to the 'Unregistered' LA 
 | 
     */ 
 | 
    status = smb_wr16(SECOCEC_DEVICE_LA, logical_addr & 0xf); 
 | 
    if (status) 
 | 
        return status; 
 | 
  
 | 
    /* Re-enable device */ 
 | 
    status = smb_wr16(SECOCEC_ENABLE_REG_1, 
 | 
              enable_val | SECOCEC_ENABLE_REG_1_CEC); 
 | 
    if (status) 
 | 
        return status; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int secocec_adap_transmit(struct cec_adapter *adap, u8 attempts, 
 | 
                 u32 signal_free_time, struct cec_msg *msg) 
 | 
{ 
 | 
    u16 payload_len, payload_id_len, destination, val = 0; 
 | 
    u8 *payload_msg; 
 | 
    int status; 
 | 
    u8 i; 
 | 
  
 | 
    /* Device msg len already accounts for header */ 
 | 
    payload_id_len = msg->len - 1; 
 | 
  
 | 
    /* Send data length */ 
 | 
    status = smb_wr16(SECOCEC_WRITE_DATA_LENGTH, payload_id_len); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    /* Send Operation ID if present */ 
 | 
    if (payload_id_len > 0) { 
 | 
        status = smb_wr16(SECOCEC_WRITE_OPERATION_ID, msg->msg[1]); 
 | 
        if (status) 
 | 
            goto err; 
 | 
    } 
 | 
    /* Send data if present */ 
 | 
    if (payload_id_len > 1) { 
 | 
        /* Only data; */ 
 | 
        payload_len = msg->len - 2; 
 | 
        payload_msg = &msg->msg[2]; 
 | 
  
 | 
        /* Copy message into registers */ 
 | 
        for (i = 0; i < payload_len; i += 2) { 
 | 
            /* hi byte */ 
 | 
            val = payload_msg[i + 1] << 8; 
 | 
  
 | 
            /* lo byte */ 
 | 
            val |= payload_msg[i]; 
 | 
  
 | 
            status = smb_wr16(SECOCEC_WRITE_DATA_00 + i / 2, val); 
 | 
            if (status) 
 | 
                goto err; 
 | 
        } 
 | 
    } 
 | 
    /* Send msg source/destination and fire msg */ 
 | 
    destination = msg->msg[0]; 
 | 
    status = smb_wr16(SECOCEC_WRITE_BYTE0, destination); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err: 
 | 
    return status; 
 | 
} 
 | 
  
 | 
static void secocec_tx_done(struct cec_adapter *adap, u16 status_val) 
 | 
{ 
 | 
    if (status_val & SECOCEC_STATUS_TX_ERROR_MASK) { 
 | 
        if (status_val & SECOCEC_STATUS_TX_NACK_ERROR) 
 | 
            cec_transmit_attempt_done(adap, CEC_TX_STATUS_NACK); 
 | 
        else 
 | 
            cec_transmit_attempt_done(adap, CEC_TX_STATUS_ERROR); 
 | 
    } else { 
 | 
        cec_transmit_attempt_done(adap, CEC_TX_STATUS_OK); 
 | 
    } 
 | 
  
 | 
    /* Reset status reg */ 
 | 
    status_val = SECOCEC_STATUS_TX_ERROR_MASK | 
 | 
        SECOCEC_STATUS_MSG_SENT_MASK | 
 | 
        SECOCEC_STATUS_TX_NACK_ERROR; 
 | 
    smb_wr16(SECOCEC_STATUS, status_val); 
 | 
} 
 | 
  
 | 
static void secocec_rx_done(struct cec_adapter *adap, u16 status_val) 
 | 
{ 
 | 
    struct secocec_data *cec = cec_get_drvdata(adap); 
 | 
    struct device *dev = cec->dev; 
 | 
    struct cec_msg msg = { }; 
 | 
    bool flag_overflow = false; 
 | 
    u8 payload_len, i = 0; 
 | 
    u8 *payload_msg; 
 | 
    u16 val = 0; 
 | 
    int status; 
 | 
  
 | 
    if (status_val & SECOCEC_STATUS_RX_OVERFLOW_MASK) { 
 | 
        /* NOTE: Untested, it also might not be necessary */ 
 | 
        dev_warn(dev, "Received more than 16 bytes. Discarding"); 
 | 
        flag_overflow = true; 
 | 
    } 
 | 
  
 | 
    if (status_val & SECOCEC_STATUS_RX_ERROR_MASK) { 
 | 
        dev_warn(dev, "Message received with errors. Discarding"); 
 | 
        status = -EIO; 
 | 
        goto rxerr; 
 | 
    } 
 | 
  
 | 
    /* Read message length */ 
 | 
    status = smb_rd16(SECOCEC_READ_DATA_LENGTH, &val); 
 | 
    if (status) 
 | 
        return; 
 | 
  
 | 
    /* Device msg len already accounts for the header */ 
 | 
    msg.len = min(val + 1, CEC_MAX_MSG_SIZE); 
 | 
  
 | 
    /* Read logical address */ 
 | 
    status = smb_rd16(SECOCEC_READ_BYTE0, &val); 
 | 
    if (status) 
 | 
        return; 
 | 
  
 | 
    /* device stores source LA and destination */ 
 | 
    msg.msg[0] = val; 
 | 
  
 | 
    /* Read operation ID */ 
 | 
    status = smb_rd16(SECOCEC_READ_OPERATION_ID, &val); 
 | 
    if (status) 
 | 
        return; 
 | 
  
 | 
    msg.msg[1] = val; 
 | 
  
 | 
    /* Read data if present */ 
 | 
    if (msg.len > 1) { 
 | 
        payload_len = msg.len - 2; 
 | 
        payload_msg = &msg.msg[2]; 
 | 
  
 | 
        /* device stores 2 bytes in every 16-bit val */ 
 | 
        for (i = 0; i < payload_len; i += 2) { 
 | 
            status = smb_rd16(SECOCEC_READ_DATA_00 + i / 2, &val); 
 | 
            if (status) 
 | 
                return; 
 | 
  
 | 
            /* low byte, skipping header */ 
 | 
            payload_msg[i] = val & 0x00ff; 
 | 
  
 | 
            /* hi byte */ 
 | 
            payload_msg[i + 1] = (val & 0xff00) >> 8; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    cec_received_msg(cec->cec_adap, &msg); 
 | 
  
 | 
    /* Reset status reg */ 
 | 
    status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK; 
 | 
    if (flag_overflow) 
 | 
        status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_STATUS, status_val); 
 | 
  
 | 
    return; 
 | 
  
 | 
rxerr: 
 | 
    /* Reset error reg */ 
 | 
    status_val = SECOCEC_STATUS_MSG_RECEIVED_MASK | 
 | 
        SECOCEC_STATUS_RX_ERROR_MASK; 
 | 
    if (flag_overflow) 
 | 
        status_val |= SECOCEC_STATUS_RX_OVERFLOW_MASK; 
 | 
    smb_wr16(SECOCEC_STATUS, status_val); 
 | 
} 
 | 
  
 | 
static const struct cec_adap_ops secocec_cec_adap_ops = { 
 | 
    /* Low-level callbacks */ 
 | 
    .adap_enable = secocec_adap_enable, 
 | 
    .adap_log_addr = secocec_adap_log_addr, 
 | 
    .adap_transmit = secocec_adap_transmit, 
 | 
}; 
 | 
  
 | 
#ifdef CONFIG_CEC_SECO_RC 
 | 
static int secocec_ir_probe(void *priv) 
 | 
{ 
 | 
    struct secocec_data *cec = priv; 
 | 
    struct device *dev = cec->dev; 
 | 
    int status; 
 | 
    u16 val; 
 | 
  
 | 
    /* Prepare the RC input device */ 
 | 
    cec->ir = devm_rc_allocate_device(dev, RC_DRIVER_SCANCODE); 
 | 
    if (!cec->ir) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    snprintf(cec->ir_input_phys, sizeof(cec->ir_input_phys), 
 | 
         "%s/input0", dev_name(dev)); 
 | 
  
 | 
    cec->ir->device_name = dev_name(dev); 
 | 
    cec->ir->input_phys = cec->ir_input_phys; 
 | 
    cec->ir->input_id.bustype = BUS_HOST; 
 | 
    cec->ir->input_id.vendor = 0; 
 | 
    cec->ir->input_id.product = 0; 
 | 
    cec->ir->input_id.version = 1; 
 | 
    cec->ir->driver_name = SECOCEC_DEV_NAME; 
 | 
    cec->ir->allowed_protocols = RC_PROTO_BIT_RC5; 
 | 
    cec->ir->priv = cec; 
 | 
    cec->ir->map_name = RC_MAP_HAUPPAUGE; 
 | 
    cec->ir->timeout = MS_TO_US(100); 
 | 
  
 | 
    /* Clear the status register */ 
 | 
    status = smb_rd16(SECOCEC_STATUS_REG_1, &val); 
 | 
    if (status != 0) 
 | 
        goto err; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
    if (status != 0) 
 | 
        goto err; 
 | 
  
 | 
    /* Enable the interrupts */ 
 | 
    status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
    if (status != 0) 
 | 
        goto err; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_ENABLE_REG_1, 
 | 
              val | SECOCEC_ENABLE_REG_1_IR); 
 | 
    if (status != 0) 
 | 
        goto err; 
 | 
  
 | 
    dev_dbg(dev, "IR enabled"); 
 | 
  
 | 
    status = devm_rc_register_device(dev, cec->ir); 
 | 
  
 | 
    if (status) { 
 | 
        dev_err(dev, "Failed to prepare input device"); 
 | 
        cec->ir = NULL; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err: 
 | 
    smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
  
 | 
    smb_wr16(SECOCEC_ENABLE_REG_1, 
 | 
         val & ~SECOCEC_ENABLE_REG_1_IR); 
 | 
  
 | 
    dev_dbg(dev, "IR disabled"); 
 | 
    return status; 
 | 
} 
 | 
  
 | 
static int secocec_ir_rx(struct secocec_data *priv) 
 | 
{ 
 | 
    struct secocec_data *cec = priv; 
 | 
    struct device *dev = cec->dev; 
 | 
    u16 val, status, key, addr, toggle; 
 | 
  
 | 
    if (!cec->ir) 
 | 
        return -ENODEV; 
 | 
  
 | 
    status = smb_rd16(SECOCEC_IR_READ_DATA, &val); 
 | 
    if (status != 0) 
 | 
        goto err; 
 | 
  
 | 
    key = val & SECOCEC_IR_COMMAND_MASK; 
 | 
    addr = (val & SECOCEC_IR_ADDRESS_MASK) >> SECOCEC_IR_ADDRESS_SHL; 
 | 
    toggle = (val & SECOCEC_IR_TOGGLE_MASK) >> SECOCEC_IR_TOGGLE_SHL; 
 | 
  
 | 
    rc_keydown(cec->ir, RC_PROTO_RC5, RC_SCANCODE_RC5(addr, key), toggle); 
 | 
  
 | 
    dev_dbg(dev, "IR key pressed: 0x%02x addr 0x%02x toggle 0x%02x", key, 
 | 
        addr, toggle); 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err: 
 | 
    dev_err(dev, "IR Receive message failed (%d)", status); 
 | 
    return -EIO; 
 | 
} 
 | 
#else 
 | 
static void secocec_ir_rx(struct secocec_data *priv) 
 | 
{ 
 | 
} 
 | 
  
 | 
static int secocec_ir_probe(void *priv) 
 | 
{ 
 | 
    return 0; 
 | 
} 
 | 
#endif 
 | 
  
 | 
static irqreturn_t secocec_irq_handler(int irq, void *priv) 
 | 
{ 
 | 
    struct secocec_data *cec = priv; 
 | 
    struct device *dev = cec->dev; 
 | 
    u16 status_val, cec_val, val = 0; 
 | 
    int status; 
 | 
  
 | 
    /*  Read status register */ 
 | 
    status = smb_rd16(SECOCEC_STATUS_REG_1, &status_val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    if (status_val & SECOCEC_STATUS_REG_1_CEC) { 
 | 
        /* Read CEC status register */ 
 | 
        status = smb_rd16(SECOCEC_STATUS, &cec_val); 
 | 
        if (status) 
 | 
            goto err; 
 | 
  
 | 
        if (cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK) 
 | 
            secocec_rx_done(cec->cec_adap, cec_val); 
 | 
  
 | 
        if (cec_val & SECOCEC_STATUS_MSG_SENT_MASK) 
 | 
            secocec_tx_done(cec->cec_adap, cec_val); 
 | 
  
 | 
        if ((~cec_val & SECOCEC_STATUS_MSG_SENT_MASK) && 
 | 
            (~cec_val & SECOCEC_STATUS_MSG_RECEIVED_MASK)) 
 | 
            dev_warn_once(dev, 
 | 
                      "Message not received or sent, but interrupt fired"); 
 | 
  
 | 
        val = SECOCEC_STATUS_REG_1_CEC; 
 | 
    } 
 | 
  
 | 
    if (status_val & SECOCEC_STATUS_REG_1_IR) { 
 | 
        val |= SECOCEC_STATUS_REG_1_IR; 
 | 
  
 | 
        secocec_ir_rx(cec); 
 | 
    } 
 | 
  
 | 
    /*  Reset status register */ 
 | 
    status = smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    return IRQ_HANDLED; 
 | 
  
 | 
err: 
 | 
    dev_err_once(dev, "IRQ: R/W SMBus operation failed (%d)", status); 
 | 
  
 | 
    /*  Reset status register */ 
 | 
    val = SECOCEC_STATUS_REG_1_CEC | SECOCEC_STATUS_REG_1_IR; 
 | 
    smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
  
 | 
    return IRQ_HANDLED; 
 | 
} 
 | 
  
 | 
struct cec_dmi_match { 
 | 
    const char *sys_vendor; 
 | 
    const char *product_name; 
 | 
    const char *devname; 
 | 
    const char *conn; 
 | 
}; 
 | 
  
 | 
static const struct cec_dmi_match secocec_dmi_match_table[] = { 
 | 
    /* UDOO X86 */ 
 | 
    { "SECO", "UDOO x86", "0000:00:02.0", "Port B" }, 
 | 
}; 
 | 
  
 | 
static struct device *secocec_cec_find_hdmi_dev(struct device *dev, 
 | 
                        const char **conn) 
 | 
{ 
 | 
    int i; 
 | 
  
 | 
    for (i = 0 ; i < ARRAY_SIZE(secocec_dmi_match_table) ; ++i) { 
 | 
        const struct cec_dmi_match *m = &secocec_dmi_match_table[i]; 
 | 
  
 | 
        if (dmi_match(DMI_SYS_VENDOR, m->sys_vendor) && 
 | 
            dmi_match(DMI_PRODUCT_NAME, m->product_name)) { 
 | 
            struct device *d; 
 | 
  
 | 
            /* Find the device, bail out if not yet registered */ 
 | 
            d = bus_find_device_by_name(&pci_bus_type, NULL, 
 | 
                            m->devname); 
 | 
            if (!d) 
 | 
                return ERR_PTR(-EPROBE_DEFER); 
 | 
  
 | 
            put_device(d); 
 | 
            *conn = m->conn; 
 | 
            return d; 
 | 
        } 
 | 
    } 
 | 
  
 | 
    return ERR_PTR(-EINVAL); 
 | 
} 
 | 
  
 | 
static int secocec_acpi_probe(struct secocec_data *sdev) 
 | 
{ 
 | 
    struct device *dev = sdev->dev; 
 | 
    struct gpio_desc *gpio; 
 | 
    int irq = 0; 
 | 
  
 | 
    gpio = devm_gpiod_get(dev, NULL, GPIOF_IN); 
 | 
    if (IS_ERR(gpio)) { 
 | 
        dev_err(dev, "Cannot request interrupt gpio"); 
 | 
        return PTR_ERR(gpio); 
 | 
    } 
 | 
  
 | 
    irq = gpiod_to_irq(gpio); 
 | 
    if (irq < 0) { 
 | 
        dev_err(dev, "Cannot find valid irq"); 
 | 
        return -ENODEV; 
 | 
    } 
 | 
    dev_dbg(dev, "irq-gpio is bound to IRQ %d", irq); 
 | 
  
 | 
    sdev->irq = irq; 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
static int secocec_probe(struct platform_device *pdev) 
 | 
{ 
 | 
    struct secocec_data *secocec; 
 | 
    struct device *dev = &pdev->dev; 
 | 
    struct device *hdmi_dev; 
 | 
    const char *conn = NULL; 
 | 
    int ret; 
 | 
    u16 val; 
 | 
  
 | 
    hdmi_dev = secocec_cec_find_hdmi_dev(&pdev->dev, &conn); 
 | 
    if (IS_ERR(hdmi_dev)) 
 | 
        return PTR_ERR(hdmi_dev); 
 | 
  
 | 
    secocec = devm_kzalloc(dev, sizeof(*secocec), GFP_KERNEL); 
 | 
    if (!secocec) 
 | 
        return -ENOMEM; 
 | 
  
 | 
    dev_set_drvdata(dev, secocec); 
 | 
  
 | 
    /* Request SMBus regions */ 
 | 
    if (!request_muxed_region(BRA_SMB_BASE_ADDR, 7, "CEC00001")) { 
 | 
        dev_err(dev, "Request memory region failed"); 
 | 
        return -ENXIO; 
 | 
    } 
 | 
  
 | 
    secocec->pdev = pdev; 
 | 
    secocec->dev = dev; 
 | 
  
 | 
    if (!has_acpi_companion(dev)) { 
 | 
        dev_dbg(dev, "Cannot find any ACPI companion"); 
 | 
        ret = -ENODEV; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    ret = secocec_acpi_probe(secocec); 
 | 
    if (ret) { 
 | 
        dev_err(dev, "Cannot assign gpio to IRQ"); 
 | 
        ret = -ENODEV; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    /* Firmware version check */ 
 | 
    ret = smb_rd16(SECOCEC_VERSION, &val); 
 | 
    if (ret) { 
 | 
        dev_err(dev, "Cannot check fw version"); 
 | 
        goto err; 
 | 
    } 
 | 
    if (val < SECOCEC_LATEST_FW) { 
 | 
        dev_err(dev, "CEC Firmware not supported (v.%04x). Use ver > v.%04x", 
 | 
            val, SECOCEC_LATEST_FW); 
 | 
        ret = -EINVAL; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    ret = devm_request_threaded_irq(dev, 
 | 
                    secocec->irq, 
 | 
                    NULL, 
 | 
                    secocec_irq_handler, 
 | 
                    IRQF_TRIGGER_RISING | IRQF_ONESHOT, 
 | 
                    dev_name(&pdev->dev), secocec); 
 | 
  
 | 
    if (ret) { 
 | 
        dev_err(dev, "Cannot request IRQ %d", secocec->irq); 
 | 
        ret = -EIO; 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    /* Allocate CEC adapter */ 
 | 
    secocec->cec_adap = cec_allocate_adapter(&secocec_cec_adap_ops, 
 | 
                         secocec, 
 | 
                         dev_name(dev), 
 | 
                         CEC_CAP_DEFAULTS | 
 | 
                         CEC_CAP_CONNECTOR_INFO, 
 | 
                         SECOCEC_MAX_ADDRS); 
 | 
  
 | 
    if (IS_ERR(secocec->cec_adap)) { 
 | 
        ret = PTR_ERR(secocec->cec_adap); 
 | 
        goto err; 
 | 
    } 
 | 
  
 | 
    secocec->notifier = cec_notifier_cec_adap_register(hdmi_dev, conn, 
 | 
                               secocec->cec_adap); 
 | 
    if (!secocec->notifier) { 
 | 
        ret = -ENOMEM; 
 | 
        goto err_delete_adapter; 
 | 
    } 
 | 
  
 | 
    ret = cec_register_adapter(secocec->cec_adap, dev); 
 | 
    if (ret) 
 | 
        goto err_notifier; 
 | 
  
 | 
    ret = secocec_ir_probe(secocec); 
 | 
    if (ret) 
 | 
        goto err_notifier; 
 | 
  
 | 
    platform_set_drvdata(pdev, secocec); 
 | 
  
 | 
    dev_dbg(dev, "Device registered"); 
 | 
  
 | 
    return ret; 
 | 
  
 | 
err_notifier: 
 | 
    cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap); 
 | 
err_delete_adapter: 
 | 
    cec_delete_adapter(secocec->cec_adap); 
 | 
err: 
 | 
    release_region(BRA_SMB_BASE_ADDR, 7); 
 | 
    dev_err(dev, "%s device probe failed\n", dev_name(dev)); 
 | 
  
 | 
    return ret; 
 | 
} 
 | 
  
 | 
static int secocec_remove(struct platform_device *pdev) 
 | 
{ 
 | 
    struct secocec_data *secocec = platform_get_drvdata(pdev); 
 | 
    u16 val; 
 | 
  
 | 
    if (secocec->ir) { 
 | 
        smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
  
 | 
        smb_wr16(SECOCEC_ENABLE_REG_1, val & ~SECOCEC_ENABLE_REG_1_IR); 
 | 
  
 | 
        dev_dbg(&pdev->dev, "IR disabled"); 
 | 
    } 
 | 
    cec_notifier_cec_adap_unregister(secocec->notifier, secocec->cec_adap); 
 | 
    cec_unregister_adapter(secocec->cec_adap); 
 | 
  
 | 
    release_region(BRA_SMB_BASE_ADDR, 7); 
 | 
  
 | 
    dev_dbg(&pdev->dev, "CEC device removed"); 
 | 
  
 | 
    return 0; 
 | 
} 
 | 
  
 | 
#ifdef CONFIG_PM_SLEEP 
 | 
static int secocec_suspend(struct device *dev) 
 | 
{ 
 | 
    int status; 
 | 
    u16 val; 
 | 
  
 | 
    dev_dbg(dev, "Device going to suspend, disabling"); 
 | 
  
 | 
    /* Clear the status register */ 
 | 
    status = smb_rd16(SECOCEC_STATUS_REG_1, &val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    /* Disable the interrupts */ 
 | 
    status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_ENABLE_REG_1, val & 
 | 
              ~SECOCEC_ENABLE_REG_1_CEC & ~SECOCEC_ENABLE_REG_1_IR); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err: 
 | 
    dev_err(dev, "Suspend failed (err: %d)", status); 
 | 
    return status; 
 | 
} 
 | 
  
 | 
static int secocec_resume(struct device *dev) 
 | 
{ 
 | 
    int status; 
 | 
    u16 val; 
 | 
  
 | 
    dev_dbg(dev, "Resuming device from suspend"); 
 | 
  
 | 
    /* Clear the status register */ 
 | 
    status = smb_rd16(SECOCEC_STATUS_REG_1, &val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_STATUS_REG_1, val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    /* Enable the interrupts */ 
 | 
    status = smb_rd16(SECOCEC_ENABLE_REG_1, &val); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    status = smb_wr16(SECOCEC_ENABLE_REG_1, val | SECOCEC_ENABLE_REG_1_CEC); 
 | 
    if (status) 
 | 
        goto err; 
 | 
  
 | 
    dev_dbg(dev, "Device resumed from suspend"); 
 | 
  
 | 
    return 0; 
 | 
  
 | 
err: 
 | 
    dev_err(dev, "Resume failed (err: %d)", status); 
 | 
    return status; 
 | 
} 
 | 
  
 | 
static SIMPLE_DEV_PM_OPS(secocec_pm_ops, secocec_suspend, secocec_resume); 
 | 
#define SECOCEC_PM_OPS (&secocec_pm_ops) 
 | 
#else 
 | 
#define SECOCEC_PM_OPS NULL 
 | 
#endif 
 | 
  
 | 
#ifdef CONFIG_ACPI 
 | 
static const struct acpi_device_id secocec_acpi_match[] = { 
 | 
    {"CEC00001", 0}, 
 | 
    {}, 
 | 
}; 
 | 
  
 | 
MODULE_DEVICE_TABLE(acpi, secocec_acpi_match); 
 | 
#endif 
 | 
  
 | 
static struct platform_driver secocec_driver = { 
 | 
    .driver = { 
 | 
           .name = SECOCEC_DEV_NAME, 
 | 
           .acpi_match_table = ACPI_PTR(secocec_acpi_match), 
 | 
           .pm = SECOCEC_PM_OPS, 
 | 
    }, 
 | 
    .probe = secocec_probe, 
 | 
    .remove = secocec_remove, 
 | 
}; 
 | 
  
 | 
module_platform_driver(secocec_driver); 
 | 
  
 | 
MODULE_DESCRIPTION("SECO CEC X86 Driver"); 
 | 
MODULE_AUTHOR("Ettore Chimenti <ek5.chimenti@gmail.com>"); 
 | 
MODULE_LICENSE("Dual BSD/GPL"); 
 |