/*
 ** This work is part of the White Rabbit project
 *
 * Copyright (C) 2024 CERN (www.cern.ch)
 * Author: Quentin Genoud Duvillaret <quentin.genoud@cern.ch>
 *
 * 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 3 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/i2c-dev.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "i2c.h"

int i2c_init_bus(struct i2c_bus *bus)
{
	/* Open I2C controller (i2c-dev) file from /dev */
    bus->fd = open(bus->dev_name, O_RDWR);
    if(bus->fd < 0)
		return bus->fd;
	else
		return 0;
}

int32_t i2c_write(struct i2c_bus *bus, uint32_t address, uint32_t count, uint8_t *data)
{
	int ret;
	
	//pr_info("%s (0x%X): 0x%X 2w:%d %d\n",bus->dev_name,bus,address,to_write,data[0]);
	
	/* Check that the bus structure has been allocated */
	if(!bus)
		return I2C_NULL_PARAM;

	/* Check that the bus is initialized (device open) */
	if(bus->fd <= 0)
		return I2C_NULL_PARAM;

	/* Set the slave address */
	ret = ioctl(bus->fd, I2C_SLAVE, address);
	if(ret < 0)
		return -1;

	/* Write the data */
	ret = write(bus->fd, data, count);

	/* Check that we were able to write data */
	if(ret < 0)
		return ret;

	/* Check that we wrote as much bytes as requested */
	if(ret < count)
		return -1;

	/* All good, return number of bytes written */
	return ret;
}

int32_t i2c_read(struct i2c_bus *bus, uint32_t address, uint32_t count, uint8_t *data)
{
	int ret;
	
	//pr_info("%s (0x%X): 0x%X 2w:%d %d\n",bus->dev_name,bus,address,to_write,data[0]);
	
	/* Check that the bus structure has been allocated */
	if(!bus)
		return I2C_NULL_PARAM;

	/* Check that the bus is initialized (device open) */
	if(bus->fd <= 0)
		return I2C_NULL_PARAM;

	/* Set the slave address */
	ret = ioctl(bus->fd, I2C_SLAVE, address);
	if(ret < 0)
		return -1;

	/* Read the data */
	ret = read(bus->fd, data, count);

	/* Check that we were able to read data */
	if(ret < 0)
		return ret;

	/* Check that we read as much bytes as requested */
	if(ret < count)
		return -1;

	/* All good, return number of bytes read */
	return ret;
}

int32_t i2c_scan(struct i2c_bus * bus, uint8_t * data)
{
	const uint8_t first_valid_address = 0;
	const uint8_t last_valid_address = 0x7f;
	uint8_t address;
	int32_t found = 0;
	int res;
	uint8_t reg = 0;
	uint32_t offset;
	uint32_t bit;

	/* Check that the bus structure has been allocated */
	if(!bus)
		return I2C_NULL_PARAM;

	/* Check that the bus is initialized (device open) */
	if(bus->fd <= 0)
		return I2C_NULL_PARAM;

	/* 16 bytes * 8 addresses per byte == 128 addresses */
	memset((void *)data, 0, 16);

	/* Check all I2C addresses to find any device on the bus */
	for (address = first_valid_address; address <= last_valid_address;
		address++) {
		
		/* Set current slave address */
		res = ioctl(bus->fd, I2C_SLAVE, address);

		/* We want to perform an operation on register @ 0x00 */
		res = write(bus->fd, &reg, 1);

		/* Device detected */
		if(res > 0)
		{
			offset = address >> 3;	//choose proper byte
			bit = (1 << (address % 8));	//choose proper bit
			data[offset] |= bit;
			found++;
		}
	}
	//pr_debug("%s (%p): ndev=%d\n", bus->name, bus, found);
	return found;
}
