/*
 * Ambient light sensor driver for Nintendo Switch's Rohm BH1730
 *
 * Copyright (c) 2018 CTCaer
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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 "als.h"
#include <power/max7762x.h>
#include <soc/clock.h>
#include <soc/i2c.h>
#include <soc/pinmux.h>
#include <utils/util.h>

#define HOS_GAIN  BH1730_GAIN_64X
#define HOS_ITIME 38

void set_als_cfg(als_table_t *als_val, u8 gain, u8 itime)
{
	i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_GAIN_REG), gain);
	i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_TIMING_REG), (256 - itime));

	als_val->gain = gain;
	als_val->itime = itime;
}

void get_als_lux(als_table_t *als_val)
{
	u32 data[2];
	float pre_gain_lux;
	float visible_light;
	float ir_light;
	float light_ratio;

	u8 adc_ready = 0;
	u8 retries = 100;

	const float als_gain_idx_tbl[4] = { 1.0, 2.0, 64.0, 128.0 };
	const float als_norm_res = 100.0;
	const float als_multiplier = 3.6;
	const float als_tint = 2.7;

	// Wait for ADC to prepare new data.
	while (!(adc_ready & BH1730_CTL_ADC_VALID) && retries)
	{
		retries--;
		adc_ready = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_CONTROL_REG));
	}

	// Get visible and ir light raw data.
	data[0] = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA0LOW_REG)) +
		(i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA0HIGH_REG)) << 8);
	data[1] = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA1LOW_REG)) +
		(i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_DATA1HIGH_REG)) << 8);

	als_val->over_limit = data[0] > 65534 || data[1] > 65534;
	als_val->vi_light = data[0];
	als_val->ir_light = data[1];

	if (!data[0] || !retries)
	{
		als_val->lux = 0.0;

		return;
	}

	visible_light = (float)data[0];
	ir_light = (float)data[1];
	light_ratio = (float)data[1] / (float)data[0];

	// The following are specific to the light filter Switch uses.
	if (light_ratio < 0.5)
		pre_gain_lux = visible_light * 5.002 - ir_light * 7.502;
	else if (light_ratio < 0.754)
		pre_gain_lux = visible_light * 2.250 - ir_light * 2.000;
	else if (light_ratio < 1.029)
		pre_gain_lux = visible_light * 1.999 - ir_light * 1.667;
	else if (light_ratio < 1.373)
		pre_gain_lux = visible_light * 0.884 - ir_light * 0.583;
	else if (light_ratio < 1.879)
		pre_gain_lux = visible_light * 0.309 - ir_light * 0.165;
	else pre_gain_lux = 0.0;

	als_val->lux = (pre_gain_lux / als_gain_idx_tbl[als_val->gain]) * (als_norm_res / ((float)als_val->itime * als_tint)) * als_multiplier;
}

u8 als_init(als_table_t *als_val)
{
	// Enable power to ALS IC.
	max7762x_regulator_set_voltage(REGULATOR_LDO6, 2900000);
	max7762x_regulator_enable(REGULATOR_LDO6, true);

	// Init I2C2.
	pinmux_config_i2c(I2C_2);
	clock_enable_i2c(I2C_2);
	i2c_init(I2C_2);

	// Initialize ALS.
	u8 id = i2c_recv_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(0x12));
	i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_SPEC(BH1730_SPECCMD_RESET), 0);
	i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_GAIN_REG), HOS_GAIN);
	i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_TIMING_REG), (256 - HOS_ITIME));
	i2c_send_byte(I2C_2, BH1730_I2C_ADDR, BH1730_ADDR(BH1730_CONTROL_REG), BH1730_CTL_POWER_ON | BH1730_CTL_ADC_EN);

	als_val->gain = HOS_GAIN;
	als_val->itime = HOS_ITIME;

	return id;
}
