/*
 * Copyright (C) 2014 Andrew Duggan
 * Copyright (C) 2014 Synaptics Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include "rmidevice.h"

#define RMI_DEVICE_PDT_ENTRY_SIZE		6
#define RMI_DEVICE_PAGE_SELECT_REGISTER		0xFF
#define RMI_DEVICE_MAX_PAGE			0xFF
#define RMI_DEVICE_PAGE_SIZE			0x100
#define RMI_DEVICE_PAGE_SCAN_START		0x00e9
#define RMI_DEVICE_PAGE_SCAN_END		0x0005
#define RMI_DEVICE_F01_BASIC_QUERY_LEN		11
#define RMI_DEVICE_F01_QRY5_YEAR_MASK		0x1f
#define RMI_DEVICE_F01_QRY6_MONTH_MASK		0x0f
#define RMI_DEVICE_F01_QRY7_DAY_MASK		0x1f

#define RMI_DEVICE_F01_QRY1_HAS_LTS		(1 << 2)
#define RMI_DEVICE_F01_QRY1_HAS_SENSOR_ID	(1 << 3)
#define RMI_DEVICE_F01_QRY1_HAS_CHARGER_INP	(1 << 4)
#define RMI_DEVICE_F01_QRY1_HAS_ADJ_DOZE	(1 << 5)
#define RMI_DEVICE_F01_QRY1_HAS_ADJ_DOZE_HOFF	(1 << 6)
#define RMI_DEVICE_F01_QRY1_HAS_PROPS_2		(1 << 7)

#define RMI_DEVICE_F01_LTS_RESERVED_SIZE	19

#define RMI_DEVICE_F01_QRY42_DS4_QUERIES	(1 << 0)
#define RMI_DEVICE_F01_QRY42_MULTI_PHYS		(1 << 1)

#define RMI_DEVICE_F01_QRY43_01_PACKAGE_ID     (1 << 0)
#define RMI_DEVICE_F01_QRY43_01_BUILD_ID       (1 << 1)

#define PACKAGE_ID_BYTES			4
#define BUILD_ID_BYTES				3

#define RMI_F01_CMD_DEVICE_RESET	1
#define RMI_F01_DEFAULT_RESET_DELAY_MS	100

int RMIDevice::SetRMIPage(unsigned char page)
{
	int rc;

	if (m_page == page)
		return 0;

	m_page = page;
	rc = Write(RMI_DEVICE_PAGE_SELECT_REGISTER, &page, 1);
	if (rc < 0 || rc < 1) {
		m_page = -1;
		return rc;
	}
	return 0;
}

int RMIDevice::QueryBasicProperties()
{
	int rc;
	unsigned char basicQuery[RMI_DEVICE_F01_BASIC_QUERY_LEN];
	unsigned short queryAddr;
	unsigned char infoBuf[4];
	unsigned short prodInfoAddr;
	RMIFunction f01;

	SetRMIPage(0x00);

	if (GetFunction(f01, 1)) {
		queryAddr = f01.GetQueryBase();

		rc = Read(queryAddr, basicQuery, RMI_DEVICE_F01_BASIC_QUERY_LEN);
		if (rc < 0 || rc < RMI_DEVICE_F01_BASIC_QUERY_LEN) {
			fprintf(stderr, "Failed to read the basic query: %s\n", strerror(errno));
			return rc;
		}
		m_manufacturerID = basicQuery[0];
		m_hasLTS = basicQuery[1] & RMI_DEVICE_F01_QRY1_HAS_LTS;
		m_hasSensorID = basicQuery[1] & RMI_DEVICE_F01_QRY1_HAS_SENSOR_ID;
		m_hasAdjustableDoze = basicQuery[1] & RMI_DEVICE_F01_QRY1_HAS_ADJ_DOZE;
		m_hasAdjustableDozeHoldoff = basicQuery[1] & RMI_DEVICE_F01_QRY1_HAS_ADJ_DOZE_HOFF;
		m_hasQuery42 = basicQuery[1] & RMI_DEVICE_F01_QRY1_HAS_PROPS_2;
		m_firmwareVersionMajor = basicQuery[2];
		m_firmwareVersionMinor = basicQuery[3];

		snprintf(m_dom, sizeof(m_dom), "20%02d/%02d/%02d",
				basicQuery[5] & RMI_DEVICE_F01_QRY5_YEAR_MASK,
		 		basicQuery[6] & RMI_DEVICE_F01_QRY6_MONTH_MASK,
		 		basicQuery[7] & RMI_DEVICE_F01_QRY7_DAY_MASK);

		queryAddr += 11;
		rc = Read(queryAddr, m_productID, RMI_PRODUCT_ID_LENGTH);
		if (rc < 0 || rc < RMI_PRODUCT_ID_LENGTH) {
			fprintf(stderr, "Failed to read the product id: %s\n", strerror(errno));
			return rc;
		}
		m_productID[RMI_PRODUCT_ID_LENGTH] = '\0';

		prodInfoAddr = queryAddr + 6;
		queryAddr += 10;

		if (m_hasLTS)
			++queryAddr;

		if (m_hasSensorID) {
			rc = Read(queryAddr++, &m_sensorID, 1);
			if (rc < 0 || rc < 1) {
				fprintf(stderr, "Failed to read sensor id: %s\n", strerror(errno));
				return rc;
			}
		}

		if (m_hasLTS)
			queryAddr += RMI_DEVICE_F01_LTS_RESERVED_SIZE;

		if (m_hasQuery42) {
			rc = Read(queryAddr++, infoBuf, 1);
			if (rc < 0 || rc < 1) {
				fprintf(stderr, "Failed to read query 42: %s\n", strerror(errno));
				return rc;
			}

			m_hasDS4Queries = infoBuf[0] & RMI_DEVICE_F01_QRY42_DS4_QUERIES;
			m_hasMultiPhysical = infoBuf[0] & RMI_DEVICE_F01_QRY42_MULTI_PHYS;
		}

		if (m_hasDS4Queries) {
			rc = Read(queryAddr++, &m_ds4QueryLength, 1);
			if (rc < 0 || rc < 1) {
				fprintf(stderr, "Failed to read DS4 query length: %s\n", strerror(errno));
				return rc;
			}
		}

		for (int i = 1; i <= m_ds4QueryLength; ++i) {
			unsigned char val;
			rc = Read(queryAddr++, &val, 1);
			if (rc < 0 || rc < 1) {
				fprintf(stderr, "Failed to read F01 Query43.%02d: %s\n", i, strerror(errno));
				continue;
			}

			switch(i) {
				case 1:
					m_hasPackageIDQuery = val & RMI_DEVICE_F01_QRY43_01_PACKAGE_ID;
					m_hasBuildIDQuery = val & RMI_DEVICE_F01_QRY43_01_BUILD_ID;
					break;
				case 2:
				case 3:
				default:
					break;
			}
		}

		if (m_hasPackageIDQuery) {
			rc = Read(prodInfoAddr++, infoBuf, PACKAGE_ID_BYTES);
			if (rc >= PACKAGE_ID_BYTES) {
				unsigned short *val = (unsigned short *)infoBuf;
				m_packageID = *val;
				val = (unsigned short *)(infoBuf + 2);
				m_packageRev = *val;
			}
		}

		if (m_hasBuildIDQuery) {
			rc = Read(prodInfoAddr, infoBuf, BUILD_ID_BYTES);
			if (rc >= BUILD_ID_BYTES) {
				unsigned short *val = (unsigned short *)infoBuf;
				m_buildID = *val;
				m_buildID += infoBuf[2] * 65536;
			}
		}
	}
	return 0;
}

void RMIDevice::PrintProperties()
{
	fprintf(stdout, "manufacturerID:\t\t%d\n", m_manufacturerID);
	fprintf(stdout, "Has LTS?:\t\t%d\n", m_hasLTS);
	fprintf(stdout, "Has Sensor ID?:\t\t%d\n", m_hasSensorID);
	fprintf(stdout, "Has Adjustable Doze?:\t%d\n", m_hasAdjustableDoze);
	fprintf(stdout, "Has Query 42?:\t\t%d\n", m_hasQuery42);
	fprintf(stdout, "Date of Manufacturer:\t%s\n", m_dom);
	fprintf(stdout, "Product ID:\t\t%s\n", m_productID);
	fprintf(stdout, "Firmware Version:\t%d.%d\n", m_firmwareVersionMajor, m_firmwareVersionMinor);
	fprintf(stdout, "Package ID:\t\t%d\n", m_packageID);
	fprintf(stdout, "Package Rev:\t\t%d\n", m_packageRev);
	fprintf(stdout, "Build ID:\t\t%ld\n", m_buildID);
	fprintf(stdout, "Sensor ID:\t\t%d\n", m_sensorID);
	fprintf(stdout, "Has DS4 Queries?:\t%d\n", m_hasDS4Queries);
	fprintf(stdout, "Has Multi Phys?:\t%d\n", m_hasMultiPhysical);
	fprintf(stdout, "\n");
}

int RMIDevice::Reset()
{
	int rc;
	RMIFunction f01;
	const unsigned char deviceReset = RMI_F01_CMD_DEVICE_RESET;

	if (!GetFunction(f01, 1))
		return -1;

	fprintf(stdout, "Resetting...\n");
	rc = Write(f01.GetCommandBase(), &deviceReset, 1);
	if (rc < 0 || rc < 1)
		return rc;

	rc = Sleep(RMI_F01_DEFAULT_RESET_DELAY_MS);
	if (rc < 0)
		return -1;
	fprintf(stdout, "Reset completed.\n");
	return 0;
}

bool RMIDevice::GetFunction(RMIFunction &func, int functionNumber)
{
	std::vector<RMIFunction>::iterator funcIter;

	for (funcIter = m_functionList.begin(); funcIter != m_functionList.end(); ++funcIter) {
		if (funcIter->GetFunctionNumber() == functionNumber) {
			func = *funcIter;
			return true;
		}
	}
	return false;
}

void RMIDevice::PrintFunctions()
{
	std::vector<RMIFunction>::iterator funcIter;

	for (funcIter = m_functionList.begin(); funcIter != m_functionList.end(); ++funcIter)
		fprintf(stdout, "0x%02x (%d) (%d) (0x%x): 0x%02x 0x%02x 0x%02x 0x%02x\n",
				funcIter->GetFunctionNumber(), funcIter->GetFunctionVersion(),
				funcIter->GetInterruptSourceCount(),
				funcIter->GetInterruptMask(),
				funcIter->GetDataBase(),
				funcIter->GetControlBase(), funcIter->GetCommandBase(),
				funcIter->GetQueryBase());
}

int RMIDevice::ScanPDT(int endFunc, int endPage)
{
	int rc;
	unsigned int page;
	unsigned int maxPage;
	unsigned int addr;
	unsigned char entry[RMI_DEVICE_PDT_ENTRY_SIZE];
	unsigned int interruptCount = 0;

	maxPage = (unsigned int)((endPage < 0) ? RMI_DEVICE_MAX_PAGE : endPage);

	m_functionList.clear();

	for (page = 0; page < maxPage; ++page) {
		unsigned int page_start = RMI_DEVICE_PAGE_SIZE * page;
		unsigned int pdt_start = page_start + RMI_DEVICE_PAGE_SCAN_START;
		unsigned int pdt_end = page_start + RMI_DEVICE_PAGE_SCAN_END;
		bool found = false;

		SetRMIPage(page);

		for (addr = pdt_start; addr >= pdt_end; addr -= RMI_DEVICE_PDT_ENTRY_SIZE) {
			rc = Read(addr, entry, RMI_DEVICE_PDT_ENTRY_SIZE);
			if (rc < 0 || rc < RMI_DEVICE_PDT_ENTRY_SIZE) {
				fprintf(stderr, "Failed to read PDT entry at address (0x%04x)\n", addr);
				return rc;
			}
			
			RMIFunction func(entry, page_start, interruptCount);
			if (func.GetFunctionNumber() == 0)
				break;

			m_functionList.push_back(func);
			interruptCount += func.GetInterruptSourceCount();
			found = true;

			if (func.GetFunctionNumber() == endFunc)
				return 0;
		}

		if (!found && (endPage < 0))
			break;
	}

	m_numInterruptRegs = (interruptCount + 7) / 8;

	return 0;
}

bool RMIDevice::InBootloader()
{
	RMIFunction f01;
	if (GetFunction(f01, 0x01)) {
		int rc;
		unsigned char status;

		rc = Read(f01.GetDataBase(), &status, 1);
		if (rc < 0 || rc < 1)
			return true;

		return !!(status & 0x40);
	}
	return true;
}

long long diff_time(struct timespec *start, struct timespec *end)
{
	long long diff;
	diff = (end->tv_sec - start->tv_sec) * 1000 * 1000;
	diff += (end->tv_nsec - start->tv_nsec) / 1000;
	return diff;
}

int Sleep(int ms)
{
	struct timespec ts;
	struct timespec rem;

	ts.tv_sec = ms / 1000;
	ts.tv_nsec = (ms % 1000) * 1000 * 1000;
	for (;;) {
		if (nanosleep(&ts, &rem) == 0) {
			break;
		} else {
			if (errno == EINTR) {
				ts = rem;
				continue;
			}
			return -1;
		}
	}
	return 0;
}

void print_buffer(const unsigned char *buf, unsigned int len)
{
	for (unsigned int i = 0; i < len; ++i) {
		fprintf(stdout, "0x%02X ", buf[i]);
		if (i % 8 == 7)
			fprintf(stdout, "\n");
	}
	fprintf(stdout, "\n");
}