// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Basic KB3310B Embedded Controller support for the YeeLoong 2F netbook
 *
 *  Copyright (C) 2008 Lemote Inc.
 *  Author: liujl <liujl@lemote.com>, 2008-04-20
 */

#include <linux/io.h>
#include <linux/export.h>
#include <linux/spinlock.h>
#include <linux/delay.h>

#include "ec_kb3310b.h"

static DEFINE_SPINLOCK(index_access_lock);
static DEFINE_SPINLOCK(port_access_lock);

unsigned char ec_read(unsigned short addr)
{
	unsigned char value;
	unsigned long flags;

	spin_lock_irqsave(&index_access_lock, flags);
	outb((addr & 0xff00) >> 8, EC_IO_PORT_HIGH);
	outb((addr & 0x00ff), EC_IO_PORT_LOW);
	value = inb(EC_IO_PORT_DATA);
	spin_unlock_irqrestore(&index_access_lock, flags);

	return value;
}
EXPORT_SYMBOL_GPL(ec_read);

void ec_write(unsigned short addr, unsigned char val)
{
	unsigned long flags;

	spin_lock_irqsave(&index_access_lock, flags);
	outb((addr & 0xff00) >> 8, EC_IO_PORT_HIGH);
	outb((addr & 0x00ff), EC_IO_PORT_LOW);
	outb(val, EC_IO_PORT_DATA);
	/*  flush the write action */
	inb(EC_IO_PORT_DATA);
	spin_unlock_irqrestore(&index_access_lock, flags);
}
EXPORT_SYMBOL_GPL(ec_write);

/*
 * This function is used for EC command writes and corresponding status queries.
 */
int ec_query_seq(unsigned char cmd)
{
	int timeout;
	unsigned char status;
	unsigned long flags;
	int ret = 0;

	spin_lock_irqsave(&port_access_lock, flags);

	/* make chip goto reset mode */
	udelay(EC_REG_DELAY);
	outb(cmd, EC_CMD_PORT);
	udelay(EC_REG_DELAY);

	/* check if the command is received by ec */
	timeout = EC_CMD_TIMEOUT;
	status = inb(EC_STS_PORT);
	while (timeout-- && (status & (1 << 1))) {
		status = inb(EC_STS_PORT);
		udelay(EC_REG_DELAY);
	}

	spin_unlock_irqrestore(&port_access_lock, flags);

	if (timeout <= 0) {
		printk(KERN_ERR "%s: deadable error : timeout...\n", __func__);
		ret = -EINVAL;
	} else
		printk(KERN_INFO
			   "(%x/%d)ec issued command %d status : 0x%x\n",
			   timeout, EC_CMD_TIMEOUT - timeout, cmd, status);

	return ret;
}
EXPORT_SYMBOL_GPL(ec_query_seq);

/*
 * Send query command to EC to get the proper event number
 */
int ec_query_event_num(void)
{
	return ec_query_seq(CMD_GET_EVENT_NUM);
}
EXPORT_SYMBOL(ec_query_event_num);

/*
 * Get event number from EC
 *
 * NOTE: This routine must follow the query_event_num function in the
 * interrupt.
 */
int ec_get_event_num(void)
{
	int timeout = 100;
	unsigned char value;
	unsigned char status;

	udelay(EC_REG_DELAY);
	status = inb(EC_STS_PORT);
	udelay(EC_REG_DELAY);
	while (timeout-- && !(status & (1 << 0))) {
		status = inb(EC_STS_PORT);
		udelay(EC_REG_DELAY);
	}
	if (timeout <= 0) {
		pr_info("%s: get event number timeout.\n", __func__);

		return -EINVAL;
	}
	value = inb(EC_DAT_PORT);
	udelay(EC_REG_DELAY);

	return value;
}
EXPORT_SYMBOL(ec_get_event_num);
