/*	$NetBSD: mcp3k.c,v 1.10 2025/09/13 14:10:44 thorpej Exp $ */

/*-
 * Copyright (c) 2015 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Frank Wille.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Microchip MCP3x0x SAR analog to digital converters.
 * The driver supports various ADCs with different resolutions, operation
 * modes and number of input channels.
 * The reference voltage Vref defaults to the maximum output value in mV,
 * but can be changed via sysctl(3).
 *
 * MCP3001: http://ww1.microchip.com/downloads/en/DeviceDoc/21293C.pdf
 * MCP3002: http://ww1.microchip.com/downloads/en/DeviceDoc/21294E.pdf
 * MCP3004/3008: http://ww1.microchip.com/downloads/en/DeviceDoc/21295C.pdf
 * MCP3201: http://ww1.microchip.com/downloads/en/DeviceDoc/21290D.pdf
 * MCP3204/3208: http://ww1.microchip.com/downloads/en/DeviceDoc/21298c.pdf
 * MCP3301: http://ww1.microchip.com/downloads/en/DeviceDoc/21700E.pdf
 * MPC3302/3304: http://ww1.microchip.com/downloads/en/DeviceDoc/21697F.pdf
 */

#include "opt_fdt.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/types.h> 
#include <sys/sysctl.h>

#include <dev/sysmon/sysmonvar.h>
#include <dev/spi/spivar.h>

#ifdef FDT
#include <dev/fdt/fdtvar.h>
#endif

#define M3K_MAX_SENSORS		16		/* 8 single-ended & 8 diff. */

/* mcp3x0x model description */
struct mcp3kadc_model {
	uint32_t		name;
	uint8_t			bits;
	uint8_t			channels;
	uint8_t			lead;		/* leading bits to ignore */
	uint8_t			flags;
#define M3K_SGLDIFF		0x01		/* single-ended/differential */
#define M3K_D2D1D0		0x02		/* 3 channel select bits */
#define M3K_MSBF		0x04		/* MSBF select bit */
#define M3K_SIGNED		0x80		/* result is signed */
#define M3K_CTRL_NEEDED		(M3K_SGLDIFF | M3K_D2D1D0 | M3K_MSBF)
};

struct mcp3kadc_softc {
	device_t			sc_dev;
	spi_handle_t			sc_sh;
	const struct mcp3kadc_model	*sc_model;
	uint32_t			sc_adc_max;
	int32_t				sc_vref_mv;
#ifdef FDT
	struct fdtbus_regulator		*sc_vref_supply;
#endif

	struct sysmon_envsys 		*sc_sme;
	envsys_data_t 			sc_sensors[M3K_MAX_SENSORS];
};

static int	mcp3kadc_match(device_t, cfdata_t, void *);
static void	mcp3kadc_attach(device_t, device_t, void *);
static void	mcp3kadc_envsys_refresh(struct sysmon_envsys *,
		    envsys_data_t *);
static int	sysctl_mcp3kadc_vref(SYSCTLFN_ARGS);

CFATTACH_DECL_NEW(mcp3kadc, sizeof(struct mcp3kadc_softc),
    mcp3kadc_match,  mcp3kadc_attach, NULL, NULL);

static const struct mcp3kadc_model mcp3001 = {
	.name = 3001,
	.bits = 10,
	.channels = 1,
	.lead = 3,
	.flags = 0
};

static const struct mcp3kadc_model mcp3002 = {
	.name = 3002,
	.bits = 10,
	.channels = 2,
	.lead = 2,
	.flags = M3K_SGLDIFF | M3K_MSBF
};

static const struct mcp3kadc_model mcp3004 = {
	.name = 3004,
	.bits = 10,
	.channels = 4,
	.lead = 2,
	.flags = M3K_SGLDIFF | M3K_D2D1D0
};

static const struct mcp3kadc_model mcp3008 = {
	.name = 3008,
	.bits = 10,
	.channels = 8,
	.lead = 2,
	.flags = M3K_SGLDIFF | M3K_D2D1D0
};

static const struct mcp3kadc_model mcp3201 = {
	.name = 3201,
	.bits = 12,
	.channels = 1,
	.lead = 3,
	.flags = 0
};

static const struct mcp3kadc_model mcp3202 = {
	.name = 3202,
	.bits = 12,
	.channels = 2,
	.lead = 2,
	.flags = M3K_SGLDIFF | M3K_MSBF
};

static const struct mcp3kadc_model mcp3204 = {
	.name = 3204,
	.bits = 12,
	.channels = 4,
	.lead = 2,
	.flags = M3K_SGLDIFF | M3K_D2D1D0
};

static const struct mcp3kadc_model mcp3208 = {
	.name = 3208,
	.bits = 12,
	.channels = 8,
	.lead = 2,
	.flags = M3K_SGLDIFF | M3K_D2D1D0
};

static const struct mcp3kadc_model mcp3301 = {
	.name = 3301,
	.bits = 13,
	.channels = 1,
	.lead = 3,
	.flags = M3K_SIGNED
};

static const struct mcp3kadc_model mcp3302 = {
	.name = 3302,
	.bits = 13,
	.channels = 4,
	.lead = 2,
	.flags = M3K_SIGNED | M3K_SGLDIFF | M3K_D2D1D0
};

static const struct mcp3kadc_model mcp3304 = {
	.name = 3304,
	.bits = 13,
	.channels = 8,
	.lead = 2,
	.flags = M3K_SIGNED | M3K_SGLDIFF | M3K_D2D1D0
};

/*
 * N.B. The order of this table is important!  It matches the order
 * of the legacy mcp3k_models[] array, which is used to manually
 * select the device type in the kernel configuration file when
 * direct configuration is not available.
 */
static const struct device_compatible_entry compat_data[] = {
	{ .compat = "microchip,mcp3001",	.data = &mcp3001 },
	{ .compat = "microchip,mcp3002",	.data = &mcp3002 },
	{ .compat = "microchip,mcp3004",	.data = &mcp3004 },
	{ .compat = "microchip,mcp3008",	.data = &mcp3008 },
	{ .compat = "microchip,mcp3201",	.data = &mcp3201 },
	{ .compat = "microchip,mcp3202",	.data = &mcp3202 },
	{ .compat = "microchip,mcp3204",	.data = &mcp3204 },
	{ .compat = "microchip,mcp3208",	.data = &mcp3208 },
	{ .compat = "microchip,mcp3301",	.data = &mcp3301 },
	{ .compat = "microchip,mcp3302",	.data = &mcp3302 },
	{ .compat = "microchip,mcp3304",	.data = &mcp3304 },

#if 0	/* We should also add support for these: */
	{ .compat = "microchip,mcp3550-50" },
	{ .compat = "microchip,mcp3550-60" },
	{ .compat = "microchip,mcp3551" },
	{ .compat = "microchip,mcp3553" },
#endif

	DEVICE_COMPAT_EOL
};
static const int mcp3k_nmodels = __arraycount(compat_data) - 1;

static const struct mcp3kadc_model *
mcp3kadc_lookup(const struct spi_attach_args *sa, const cfdata_t cf)
{
	const struct mcp3kadc_model *model = NULL;
	const struct device_compatible_entry *dce =
	    spi_compatible_lookup(sa, compat_data);

	if (dce != NULL) {
		model = dce->data;
	} else if (cf->cf_flags >= 0 && cf->cf_flags < mcp3k_nmodels) {
		model = compat_data[cf->cf_flags].data;
	}
	return model;
}

static int
mcp3kadc_match(device_t parent, cfdata_t cf, void *aux)
{
	struct spi_attach_args *sa = aux;
	int match_result;

	if (spi_use_direct_match(sa, compat_data, &match_result)) {
		return match_result;
	}

	/*
	 * If we're doing indirect config, the user must
	 * have specified a valid model.
	 */
	if (mcp3kadc_lookup(sa, cf) == NULL) {
		return 0;
	}

	return SPI_MATCH_DEFAULT;
}

#ifdef FDT
static bool
mcp3kadc_vref_fdt(struct mcp3kadc_softc *sc)
{
	devhandle_t devhandle = device_handle(sc->sc_dev);
	int phandle = devhandle_to_of(devhandle);
	int error;
	u_int uvolts;

	sc->sc_vref_supply = fdtbus_regulator_acquire(phandle, "vref-supply");
	if (sc->sc_vref_supply == NULL) {
		aprint_error_dev(sc->sc_dev,
		    "unable to acquire \"vref-supply\"\n");
		return false;
	}

	error = fdtbus_regulator_enable(sc->sc_vref_supply);
	if (error) {
		aprint_error_dev(sc->sc_dev,
		    "failed to enable \"vref-supply\" (error = %d)\n",
		    error);
		return false;
	}

	error = fdtbus_regulator_get_voltage(sc->sc_vref_supply, &uvolts);
	if (error) {
		aprint_error_dev(sc->sc_dev,
		    "unable to get \"vref-supply\" voltage (error = %d)\n",
		    error);
		(void) fdtbus_regulator_disable(sc->sc_vref_supply);
		return false;
	}

	/*
	 * Device tree property is uV, convert to mV that we use
	 * internally.
	 */
	sc->sc_vref_mv = uvolts / 1000;
	return true;
}
#endif /* FDT */

static void
mcp3kadc_attach(device_t parent, device_t self, void *aux)
{
	const struct sysctlnode *rnode, *node;
	struct spi_attach_args *sa = aux;
	struct mcp3kadc_softc *sc = device_private(self);
	devhandle_t devhandle = device_handle(self);
	const struct mcp3kadc_model *model;
	int error, ch, i;
	bool vref_read_only;

	sc->sc_dev = self;
	sc->sc_sh = sa->sa_handle;

	model = mcp3kadc_lookup(sa, device_cfdata(self));
	KASSERT(model != NULL);

	sc->sc_model = model;

	aprint_naive(": Analog to Digital converter\n");
	aprint_normal(": MCP%u %u-channel %u-bit ADC\n",
	    (unsigned)model->name, (unsigned)model->channels,
	    (unsigned)model->bits);

	/* configure for 1MHz */
	error = spi_configure(self, sa->sa_handle, SPI_MODE_0, SPI_FREQ_MHz(1));
	if (error) {
		return;
	}

	vref_read_only = false;
	switch (devhandle_type(devhandle)) {
#ifdef FDT
	case DEVHANDLE_TYPE_OF:
		vref_read_only = mcp3kadc_vref_fdt(sc);
		if (! vref_read_only) {
			/* Error already displayed. */
			return;
		}
		break;
#endif /* FDT */
	default:
		/*
		 * Set a default Vref in mV according to the chip's ADC
		 * resolution.
		 */
		sc->sc_vref_mv = 1 << ((model->flags & M3K_SIGNED) ?
		    model->bits - 1 : model->bits);
		break;
	}

	/* remember maximum value for this ADC - also used for masking */
	sc->sc_adc_max = (1 << model->bits) - 1;

	/* attach voltage sensors to envsys */
	sc->sc_sme = sysmon_envsys_create();

	/* adc difference from two neighbouring channels */
	for (ch = 0; ch < model->channels; ch++) {
		KASSERT(ch < M3K_MAX_SENSORS);
		sc->sc_sensors[ch].units = ENVSYS_SVOLTS_DC;
		sc->sc_sensors[ch].state = ENVSYS_SINVALID;
		if (model->channels == 1)
			strlcpy(sc->sc_sensors[ch].desc, "adc diff ch0",
			    sizeof(sc->sc_sensors[ch].desc));
		else
			snprintf(sc->sc_sensors[ch].desc,
			    sizeof(sc->sc_sensors[ch].desc),
			    "adc diff ch%d-ch%d", ch, ch ^ 1);
		sc->sc_sensors[ch].private = ch;
		sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensors[ch]);
	}

	if (model->flags & M3K_SGLDIFF) {
		/* adc from single ended channels */
		for (i = 0; i < model->channels; i++, ch++) {
			KASSERT(ch < M3K_MAX_SENSORS);
			sc->sc_sensors[ch].units = ENVSYS_SVOLTS_DC;
			sc->sc_sensors[ch].state = ENVSYS_SINVALID;
			snprintf(sc->sc_sensors[ch].desc,
			    sizeof(sc->sc_sensors[ch].desc),
			    "adc single ch%d", i);
			sc->sc_sensors[ch].private = ch;
			sysmon_envsys_sensor_attach(sc->sc_sme,
			    &sc->sc_sensors[ch]);
		}
	}

	sc->sc_sme->sme_name = device_xname(self);
	sc->sc_sme->sme_refresh = mcp3kadc_envsys_refresh;
	sc->sc_sme->sme_cookie = sc;
	if (sysmon_envsys_register(sc->sc_sme)) {
		aprint_error_dev(self, "unable to register with sysmon\n");
		sysmon_envsys_destroy(sc->sc_sme);
	}

	/* create a sysctl node for adjusting the ADC's reference voltage */
	rnode = node = NULL;
	sysctl_createv(NULL, 0, NULL, &rnode,
	    CTLFLAG_READWRITE,
	    CTLTYPE_NODE, device_xname(sc->sc_dev), NULL,
	    NULL, 0, NULL, 0,
	    CTL_HW, CTL_CREATE, CTL_EOL);

	const int ctlflag = vref_read_only ? CTLFLAG_READONLY
					   : CTLFLAG_READWRITE;

	if (rnode != NULL)
		sysctl_createv(NULL, 0, NULL, &node,
		    ctlflag | CTLFLAG_OWNDESC,
		    CTLTYPE_INT, "vref",
		    SYSCTL_DESCR("ADC reference voltage"),
		    sysctl_mcp3kadc_vref, 0, (void *)sc, 0,
		    CTL_HW, rnode->sysctl_num, CTL_CREATE, CTL_EOL);
}

static void
mcp3kadc_envsys_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
{
	struct mcp3kadc_softc *sc;
	const struct mcp3kadc_model *model;
	uint8_t buf[2], ctrl;
	int32_t val, scale;

	sc = sme->sme_cookie;
	model = sc->sc_model;
	scale = sc->sc_adc_max + 1;

	if (model->flags & M3K_CTRL_NEEDED) {
		/* we need to send some control bits first */
		ctrl = 1;	/* start bit */

		if (model->flags & M3K_SGLDIFF) {
			/* bit set to select single-ended mode */
			ctrl <<= 1;
			ctrl |= edata->private >= model->channels;
		}

		if (model->flags & M3K_D2D1D0) {
			/* 3 bits select the channel */
			ctrl <<= 3;
			ctrl |= edata->private & (model->channels - 1);
		} else {
			/* 1 bit selects between two channels */
			ctrl <<= 1;
			ctrl |= edata->private & 1;
		}

		if (model->flags & M3K_MSBF) {
			/* bit select MSB first format */
			ctrl <<= 1;
			ctrl |= 1;
		}

		/* send control bits, receive ADC data */
		if (spi_send_recv(sc->sc_sh, 1, &ctrl, 2, buf) != 0) {
			edata->state = ENVSYS_SINVALID;
			return;
		}
	} else {

		/* just read data from the ADC */
		if (spi_recv(sc->sc_sh, 2, buf) != 0) {
			edata->state = ENVSYS_SINVALID;
			return;
		}
	}

	/* extract big-endian ADC data from buffer */
	val = (buf[0] << 8) | buf[1];
	val = (val >> (16 - (model->bits + model->lead))) & sc->sc_adc_max;

	/* sign-extend the result, when needed */
	if (model->flags & M3K_SIGNED) {
		if (val & (1 << (model->bits - 1)))
			val -= sc->sc_adc_max + 1;
		scale >>= 1;	/* MSB is the sign */
	}

	/* scale the value for Vref and convert to mV */
	edata->value_cur = (sc->sc_vref_mv * val / scale) * 1000;
	edata->state = ENVSYS_SVALID;
}

static int
sysctl_mcp3kadc_vref(SYSCTLFN_ARGS)
{
	struct sysctlnode node;
	struct mcp3kadc_softc *sc;
	int32_t t;
	int error;

	node = *rnode;
	sc = node.sysctl_data;

	t = sc->sc_vref_mv;
	node.sysctl_data = &t;

	error = sysctl_lookup(SYSCTLFN_CALL(&node));
	if (error || newp == NULL)
		return error;
	if (t <= 0)
		return EINVAL;

	sc->sc_vref_mv = t;
	return 0;
}
