Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 24 Mar 2014 20:06:27 +0000 (UTC)
From:      Luiz Otavio O Souza <loos@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r263693 - in head: share/man/man4/man4.arm sys/arm/conf sys/arm/ti sys/arm/ti/am335x sys/boot/fdt/dts/arm
Message-ID:  <201403242006.s2OK6RVA070560@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: loos
Date: Mon Mar 24 20:06:27 2014
New Revision: 263693
URL: http://svnweb.freebsd.org/changeset/base/263693

Log:
  Adds the ADC driver for TI AM3xxx SoC family.
  
  The ADC has a 12bit resolution and its raw output can be read via sysctl(8)
  interface.
  
  The driver allows the setup of ADC clock, samples average and open delay
  (the number of clock cycles to wait before start the conversion).
  
  The TSC_ADC module is set in the general purpose mode (no touchscreen
  support).
  
  Tested on Beaglebone-black.
  
  Written based on AM335x TRM.
  
  Reviewed by:	rpaulo
  Approved by:	adrian (mentor)
  Tested by:	me, Brian J. McGovern, Sulev-Madis Silber (ketas)

Added:
  head/share/man/man4/man4.arm/ti_adc.4   (contents, props changed)
  head/sys/arm/ti/ti_adc.c   (contents, props changed)
  head/sys/arm/ti/ti_adcreg.h   (contents, props changed)
  head/sys/arm/ti/ti_adcvar.h   (contents, props changed)
Modified:
  head/share/man/man4/man4.arm/Makefile
  head/sys/arm/conf/BEAGLEBONE
  head/sys/arm/ti/am335x/am335x_prcm.c
  head/sys/arm/ti/files.ti
  head/sys/arm/ti/ti_prcm.h
  head/sys/boot/fdt/dts/arm/am335x.dtsi

Modified: head/share/man/man4/man4.arm/Makefile
==============================================================================
--- head/share/man/man4/man4.arm/Makefile	Mon Mar 24 19:21:14 2014	(r263692)
+++ head/share/man/man4/man4.arm/Makefile	Mon Mar 24 20:06:27 2014	(r263693)
@@ -1,6 +1,9 @@
 # $FreeBSD$
 
-MAN=	mge.4 npe.4 devcfg.4
+MAN=	devcfg.4 \
+	mge.4 \
+	npe.4 \
+	ti_adc.4
 
 MLINKS= mge.4 if_mge.4
 MLINKS+=npe.4 if_npe.4

Added: head/share/man/man4/man4.arm/ti_adc.4
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/share/man/man4/man4.arm/ti_adc.4	Mon Mar 24 20:06:27 2014	(r263693)
@@ -0,0 +1,119 @@
+.\"
+.\" Copyright (c) 2014 Luiz Otavio O Souza <loos@freebsd.org>
+.\" All rights reserved.
+.\"
+.\" 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 AUTHOR ``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 AUTHOR 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd March 21, 2014
+.Dt TI_ADC 4
+.Os
+.Sh NAME
+.Nm ti_adc
+.Nd TI AM3XXX analog to digital converter driver
+.Sh SYNOPSIS
+.Cd "device ti_adc"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides access to the AIN (analog inputs) on am3xxx SoCs.
+.Pp
+It provides raw readings of the converted values for each analog inputs.
+.Pp
+The access to
+.Nm
+data is made via the
+.Xr sysctl 8
+interface:
+.Bd -literal
+dev.ti_adc.0.%desc: TI ADC controller
+dev.ti_adc.0.%driver: ti_adc
+dev.ti_adc.0.%pnpinfo: name=adc@44E0D000 compat=ti,adc
+dev.ti_adc.0.%parent: simplebus0
+dev.ti_adc.0.clockdiv: 2400
+dev.ti_adc.0.ain.0.enable: 0
+dev.ti_adc.0.ain.0.open_delay: 0
+dev.ti_adc.0.ain.0.samples_avg: 0
+dev.ti_adc.0.ain.0.input: 0
+dev.ti_adc.0.ain.1.enable: 0
+dev.ti_adc.0.ain.1.open_delay: 0
+dev.ti_adc.0.ain.1.samples_avg: 0
+dev.ti_adc.0.ain.1.input: 0
+dev.ti_adc.0.ain.2.enable: 0
+dev.ti_adc.0.ain.2.open_delay: 0
+dev.ti_adc.0.ain.2.samples_avg: 0
+dev.ti_adc.0.ain.2.input: 0
+dev.ti_adc.0.ain.3.enable: 0
+dev.ti_adc.0.ain.3.open_delay: 0
+dev.ti_adc.0.ain.3.samples_avg: 0
+dev.ti_adc.0.ain.3.input: 0
+dev.ti_adc.0.ain.4.enable: 0
+dev.ti_adc.0.ain.4.open_delay: 0
+dev.ti_adc.0.ain.4.samples_avg: 0
+dev.ti_adc.0.ain.4.input: 0
+dev.ti_adc.0.ain.5.enable: 0
+dev.ti_adc.0.ain.5.open_delay: 0
+dev.ti_adc.0.ain.5.samples_avg: 0
+dev.ti_adc.0.ain.5.input: 0
+dev.ti_adc.0.ain.6.enable: 1
+dev.ti_adc.0.ain.6.open_delay: 0
+dev.ti_adc.0.ain.6.samples_avg: 4
+dev.ti_adc.0.ain.6.input: 2308
+.Ed
+.Pp
+Global settings:
+.Bl -tag -width ".Va dev.ti_adc.0.clockdiv"
+.It Va dev.ti_adc.0.clockdiv
+Sets the ADC clock prescaler.
+The minimum value is 10 and the maximum is 65535.
+The ADC clock is based on CLK_M_OSC (24Mhz) / clockdiv.
+This gives a maximum of ~2.4Mhz for the ADC clock and ~10Khz for the default
+setting (clockdiv = 2400).
+.El
+.Pp
+Settings per input:
+.Bl -tag -width ".Va dev.ti_adc.0.ain.%d.samples_avg"
+.It Va dev.ti_adc.0.ain.%d.enable
+Enable the conversion for the input.
+Each input should be individually enabled before it can be used.
+When all the inputs are disabled, the ADC is turned off.
+.It Va dev.ti_adc.0.ain.%d.open_delay
+Sets the number of ADC clock cycles to wait after applying the input
+configuration and before start the ADC conversion.
+.It Va dev.ti_adc.0.ain.%d.samples_avg
+Sets the number of samples average used on each input, it can be set to 0
+(no samples average), 2, 4, 8, or 16.
+.It Va dev.ti_adc.0.ain.%d.input
+Is the converted raw value of the voltage applied on the analog input.
+It is made of a 12 bit value (0 ~ 4095).
+.El
+.Sh SEE ALSO
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 11.0 .
+.Sh AUTHORS
+.An -nosplit
+The driver and this manual page was written by
+.An Luiz Otavio O Souza Aq loos@FreeBSD.org

Modified: head/sys/arm/conf/BEAGLEBONE
==============================================================================
--- head/sys/arm/conf/BEAGLEBONE	Mon Mar 24 19:21:14 2014	(r263692)
+++ head/sys/arm/conf/BEAGLEBONE	Mon Mar 24 20:06:27 2014	(r263693)
@@ -105,6 +105,9 @@ device		am335x_pmic		# AM335x Power Mana
 device		gpio
 device		gpioled
 
+# ADC support
+device		ti_adc
+
 # USB support
 device		usb
 options 	USB_HOST_ALIGN=64	# Cacheline size is 64 on AM335x.

Modified: head/sys/arm/ti/am335x/am335x_prcm.c
==============================================================================
--- head/sys/arm/ti/am335x/am335x_prcm.c	Mon Mar 24 19:21:14 2014	(r263692)
+++ head/sys/arm/ti/am335x/am335x_prcm.c	Mon Mar 24 20:06:27 2014	(r263693)
@@ -107,6 +107,7 @@ __FBSDID("$FreeBSD$");
 #define CM_WKUP_CM_CLKDCOLDO_DPLL_PER	(CM_WKUP + 0x07C)
 #define CM_WKUP_CM_CLKMODE_DPLL_DISP	(CM_WKUP + 0x098)
 #define CM_WKUP_I2C0_CLKCTRL		(CM_WKUP + 0x0B8)
+#define CM_WKUP_ADC_TSC_CLKCTRL		(CM_WKUP + 0x0BC)
 
 #define CM_DPLL				0x500
 #define CLKSEL_TIMER7_CLK		(CM_DPLL + 0x004)
@@ -260,6 +261,9 @@ struct ti_clock_dev ti_clk_devmap[] = {
 	AM335X_GENERIC_CLOCK_DEV(I2C1_CLK),
 	AM335X_GENERIC_CLOCK_DEV(I2C2_CLK),
 
+	/* TSC_ADC */
+	AM335X_GENERIC_CLOCK_DEV(TSC_ADC_CLK),
+
 	/* EDMA */
 	AM335X_GENERIC_CLOCK_DEV(EDMA_TPCC_CLK),
 	AM335X_GENERIC_CLOCK_DEV(EDMA_TPTC0_CLK),
@@ -337,6 +341,9 @@ static struct am335x_clk_details g_am335
 	_CLK_DETAIL(I2C1_CLK, CM_PER_I2C1_CLKCTRL, 0),
 	_CLK_DETAIL(I2C2_CLK, CM_PER_I2C2_CLKCTRL, 0),
 
+	/* TSC_ADC module */
+	_CLK_DETAIL(TSC_ADC_CLK, CM_WKUP_ADC_TSC_CLKCTRL, 0),
+
 	/* EDMA modules */
 	_CLK_DETAIL(EDMA_TPCC_CLK, CM_PER_TPCC_CLKCTRL, 0),
 	_CLK_DETAIL(EDMA_TPTC0_CLK, CM_PER_TPTC0_CLKCTRL, 0),

Modified: head/sys/arm/ti/files.ti
==============================================================================
--- head/sys/arm/ti/files.ti	Mon Mar 24 19:21:14 2014	(r263692)
+++ head/sys/arm/ti/files.ti	Mon Mar 24 20:06:27 2014	(r263693)
@@ -19,6 +19,7 @@ dev/mbox/mbox_if.m				standard
 arm/ti/ti_mbox.c				standard
 arm/ti/ti_pruss.c				standard
 
+arm/ti/ti_adc.c					optional	ti_adc
 arm/ti/ti_gpio.c				optional	gpio
 arm/ti/ti_i2c.c					optional	ti_i2c
 

Added: head/sys/arm/ti/ti_adc.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/arm/ti/ti_adc.c	Mon Mar 24 20:06:27 2014	(r263693)
@@ -0,0 +1,593 @@
+/*-
+ * Copyright 2014 Luiz Otavio O Souza <loos@freebsd.org>
+ * All rights reserved.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+
+#include <sys/kernel.h>
+#include <sys/limits.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/resource.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <arm/ti/ti_prcm.h>
+#include <arm/ti/ti_adcreg.h>
+#include <arm/ti/ti_adcvar.h>
+
+/* Define our 7 steps, one for each input channel. */
+static struct ti_adc_input ti_adc_inputs[TI_ADC_NPINS] = {
+	{ .stepconfig = ADC_STEPCFG1, .stepdelay = ADC_STEPDLY1 },
+	{ .stepconfig = ADC_STEPCFG2, .stepdelay = ADC_STEPDLY2 },
+	{ .stepconfig = ADC_STEPCFG3, .stepdelay = ADC_STEPDLY3 },
+	{ .stepconfig = ADC_STEPCFG4, .stepdelay = ADC_STEPDLY4 },
+	{ .stepconfig = ADC_STEPCFG5, .stepdelay = ADC_STEPDLY5 },
+	{ .stepconfig = ADC_STEPCFG6, .stepdelay = ADC_STEPDLY6 },
+	{ .stepconfig = ADC_STEPCFG7, .stepdelay = ADC_STEPDLY7 },
+};
+
+static int ti_adc_samples[5] = { 0, 2, 4, 8, 16 };
+
+static void
+ti_adc_enable(struct ti_adc_softc *sc)
+{
+
+	TI_ADC_LOCK_ASSERT(sc);
+
+	if (sc->sc_last_state == 1)
+		return;
+
+	/* Enable the FIFO0 threshold and the end of sequence interrupt. */
+	ADC_WRITE4(sc, ADC_IRQENABLE_SET,
+	    ADC_IRQ_FIFO0_THRES | ADC_IRQ_END_OF_SEQ);
+
+	/* Enable the ADC.  Run thru enabled steps, start the conversions. */
+	ADC_WRITE4(sc, ADC_CTRL, ADC_READ4(sc, ADC_CTRL) | ADC_CTRL_ENABLE);
+
+	sc->sc_last_state = 1;
+}
+
+static void
+ti_adc_disable(struct ti_adc_softc *sc)
+{
+	int count;
+	uint32_t data;
+
+	TI_ADC_LOCK_ASSERT(sc);
+
+	if (sc->sc_last_state == 0)
+		return;
+
+	/* Disable all the enabled steps. */
+	ADC_WRITE4(sc, ADC_STEPENABLE, 0);
+
+	/* Disable the ADC. */
+	ADC_WRITE4(sc, ADC_CTRL, ADC_READ4(sc, ADC_CTRL) & ~ADC_CTRL_ENABLE);
+
+	/* Disable the FIFO0 threshold and the end of sequence interrupt. */
+	ADC_WRITE4(sc, ADC_IRQENABLE_CLR,
+	    ADC_IRQ_FIFO0_THRES | ADC_IRQ_END_OF_SEQ);
+
+	/* ACK any pending interrupt. */
+	ADC_WRITE4(sc, ADC_IRQSTATUS, ADC_READ4(sc, ADC_IRQSTATUS));
+
+	/* Drain the FIFO data. */
+	count = ADC_READ4(sc, ADC_FIFO0COUNT) & ADC_FIFO_COUNT_MSK;
+	while (count > 0) {
+		data = ADC_READ4(sc, ADC_FIFO0DATA);
+		count = ADC_READ4(sc, ADC_FIFO0COUNT) & ADC_FIFO_COUNT_MSK;
+	}
+
+	sc->sc_last_state = 0;
+}
+
+static int
+ti_adc_setup(struct ti_adc_softc *sc)
+{
+	int ain;
+	uint32_t enabled;
+
+	TI_ADC_LOCK_ASSERT(sc);
+
+	/* Check for enabled inputs. */
+	enabled = 0;
+	for (ain = 0; ain < TI_ADC_NPINS; ain++) {
+		if (ti_adc_inputs[ain].enable)
+			enabled |= (1U << (ain + 1));
+	}
+
+	/* Set the ADC global status. */
+	if (enabled != 0) {
+		ti_adc_enable(sc);
+		/* Update the enabled steps. */
+		if (enabled != ADC_READ4(sc, ADC_STEPENABLE))
+			ADC_WRITE4(sc, ADC_STEPENABLE, enabled);
+	} else
+		ti_adc_disable(sc);
+
+	return (0);
+}
+
+static void
+ti_adc_input_setup(struct ti_adc_softc *sc, int32_t ain)
+{
+	struct ti_adc_input *input;
+	uint32_t reg, val;
+
+	TI_ADC_LOCK_ASSERT(sc);
+
+	input = &ti_adc_inputs[ain];
+	reg = input->stepconfig;
+	val = ADC_READ4(sc, reg);
+
+	/* Set single ended operation. */
+	val &= ~ADC_STEP_DIFF_CNTRL;
+
+	/* Set the negative voltage reference. */
+	val &= ~ADC_STEP_RFM_MSK;
+	val |= ADC_STEP_RFM_VREFN << ADC_STEP_RFM_SHIFT;
+
+	/* Set the positive voltage reference. */
+	val &= ~ADC_STEP_RFP_MSK;
+	val |= ADC_STEP_RFP_VREFP << ADC_STEP_RFP_SHIFT;
+
+	/* Set the samples average. */
+	val &= ~ADC_STEP_AVG_MSK;
+	val |= input->samples << ADC_STEP_AVG_SHIFT;
+
+	/* Select the desired input. */
+	val &= ~ADC_STEP_INP_MSK;
+	val |= ain << ADC_STEP_INP_SHIFT;
+
+	/* Set the ADC to one-shot mode. */
+	val &= ~ADC_STEP_MODE_MSK;
+
+	ADC_WRITE4(sc, reg, val);
+}
+
+static void
+ti_adc_reset(struct ti_adc_softc *sc)
+{
+	int ain;
+
+	TI_ADC_LOCK_ASSERT(sc);
+
+	/* Disable all the inputs. */
+	for (ain = 0; ain < TI_ADC_NPINS; ain++)
+		ti_adc_inputs[ain].enable = 0;
+}
+
+static int
+ti_adc_clockdiv_proc(SYSCTL_HANDLER_ARGS)
+{
+	int error, reg;
+	struct ti_adc_softc *sc;
+
+	sc = (struct ti_adc_softc *)arg1;
+
+	TI_ADC_LOCK(sc);
+	reg = (int)ADC_READ4(sc, ADC_CLKDIV) + 1;
+	TI_ADC_UNLOCK(sc);
+
+	error = sysctl_handle_int(oidp, &reg, sizeof(reg), req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+
+	/*
+	 * The actual written value is the prescaler setting - 1.
+	 * Enforce a minimum value of 10 (i.e. 9) which limits the maximum
+	 * ADC clock to ~2.4Mhz (CLK_M_OSC / 10).
+	 */
+	reg--;
+	if (reg < 9)
+		reg = 9;
+	if (reg > USHRT_MAX)
+		reg = USHRT_MAX;
+
+	TI_ADC_LOCK(sc);
+	/* Disable the ADC. */
+	ti_adc_disable(sc);
+	/* Update the ADC prescaler setting. */
+	ADC_WRITE4(sc, ADC_CLKDIV, reg);
+	/* Enable the ADC again. */
+	ti_adc_setup(sc);
+	TI_ADC_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+ti_adc_enable_proc(SYSCTL_HANDLER_ARGS)
+{
+	int error;
+	int32_t enable;
+	struct ti_adc_softc *sc;
+	struct ti_adc_input *input;
+
+	input = (struct ti_adc_input *)arg1;
+	sc = input->sc;
+
+	enable = input->enable;
+	error = sysctl_handle_int(oidp, &enable, sizeof(enable),
+	    req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+
+	if (enable)
+		enable = 1;
+
+	TI_ADC_LOCK(sc);
+	/* Setup the ADC as needed. */
+	if (input->enable != enable) {
+		input->enable = enable;
+		ti_adc_setup(sc);
+		if (input->enable == 0)
+			input->value = 0;
+	}
+	TI_ADC_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+ti_adc_open_delay_proc(SYSCTL_HANDLER_ARGS)
+{
+	int error, reg;
+	struct ti_adc_softc *sc;
+	struct ti_adc_input *input;
+
+	input = (struct ti_adc_input *)arg1;
+	sc = input->sc;
+
+	TI_ADC_LOCK(sc);
+	reg = (int)ADC_READ4(sc, input->stepdelay) & ADC_STEP_OPEN_DELAY;
+	TI_ADC_UNLOCK(sc);
+
+	error = sysctl_handle_int(oidp, &reg, sizeof(reg), req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+
+	if (reg < 0)
+		reg = 0;
+
+	TI_ADC_LOCK(sc);
+	ADC_WRITE4(sc, input->stepdelay, reg & ADC_STEP_OPEN_DELAY);
+	TI_ADC_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+ti_adc_samples_avg_proc(SYSCTL_HANDLER_ARGS)
+{
+	int error, samples, i;
+	struct ti_adc_softc *sc;
+	struct ti_adc_input *input;
+
+	input = (struct ti_adc_input *)arg1;
+	sc = input->sc;
+
+	if (input->samples > nitems(ti_adc_samples))
+		input->samples = nitems(ti_adc_samples);
+	samples = ti_adc_samples[input->samples];
+
+	error = sysctl_handle_int(oidp, &samples, 0, req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+
+	TI_ADC_LOCK(sc);
+	if (samples != ti_adc_samples[input->samples]) {
+		input->samples = 0;
+		for (i = 0; i < nitems(ti_adc_samples); i++)
+			if (samples >= ti_adc_samples[i])
+				input->samples = i;
+		ti_adc_input_setup(sc, input->input);
+	}
+	TI_ADC_UNLOCK(sc);
+
+	return (error);
+}
+
+static void
+ti_adc_read_data(struct ti_adc_softc *sc)
+{
+	int count, ain;
+	struct ti_adc_input *input;
+	uint32_t data;
+
+	TI_ADC_LOCK_ASSERT(sc);
+
+	/* Read the available data. */
+	count = ADC_READ4(sc, ADC_FIFO0COUNT) & ADC_FIFO_COUNT_MSK;
+	while (count > 0) {
+		data = ADC_READ4(sc, ADC_FIFO0DATA);
+		ain = (data & ADC_FIFO_STEP_ID_MSK) >> ADC_FIFO_STEP_ID_SHIFT;
+		input = &ti_adc_inputs[ain];
+		if (input->enable == 0)
+			input->value = 0;
+		else
+			input->value = (int32_t)(data & ADC_FIFO_DATA_MSK);
+		count = ADC_READ4(sc, ADC_FIFO0COUNT) & ADC_FIFO_COUNT_MSK;
+	}
+}
+
+static void
+ti_adc_intr(void *arg)
+{
+	struct ti_adc_softc *sc;
+	uint32_t status;
+
+	sc = (struct ti_adc_softc *)arg;
+
+	status = ADC_READ4(sc, ADC_IRQSTATUS);
+	if (status == 0)
+		return;
+	if (status & ~(ADC_IRQ_FIFO0_THRES | ADC_IRQ_END_OF_SEQ))
+		device_printf(sc->sc_dev, "stray interrupt: %#x\n", status);
+
+	TI_ADC_LOCK(sc);
+	/* ACK the interrupt. */
+	ADC_WRITE4(sc, ADC_IRQSTATUS, status);
+
+	/* Read the available data. */
+	if (status & ADC_IRQ_FIFO0_THRES)
+		ti_adc_read_data(sc);
+
+	/* Start the next conversion ? */
+	if (status & ADC_IRQ_END_OF_SEQ)
+		ti_adc_setup(sc);
+	TI_ADC_UNLOCK(sc);
+}
+
+static void
+ti_adc_sysctl_init(struct ti_adc_softc *sc)
+{
+	char pinbuf[3];
+	struct sysctl_ctx_list *ctx;
+	struct sysctl_oid *tree_node, *inp_node, *inpN_node;
+	struct sysctl_oid_list *tree, *inp_tree, *inpN_tree;
+	int ain;
+
+	/*
+	 * Add per-pin sysctl tree/handlers.
+	 */
+	ctx = device_get_sysctl_ctx(sc->sc_dev);
+	tree_node = device_get_sysctl_tree(sc->sc_dev);
+	tree = SYSCTL_CHILDREN(tree_node);
+	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "clockdiv",
+	    CTLFLAG_RW | CTLTYPE_UINT,  sc, 0,
+	    ti_adc_clockdiv_proc, "IU", "ADC clock prescaler");
+	inp_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "ain",
+	    CTLFLAG_RD, NULL, "ADC inputs");
+	inp_tree = SYSCTL_CHILDREN(inp_node);
+
+	for (ain = 0; ain < TI_ADC_NPINS; ain++) {
+
+		snprintf(pinbuf, sizeof(pinbuf), "%d", ain);
+		inpN_node = SYSCTL_ADD_NODE(ctx, inp_tree, OID_AUTO, pinbuf,
+		    CTLFLAG_RD, NULL, "ADC input");
+		inpN_tree = SYSCTL_CHILDREN(inpN_node);
+
+		SYSCTL_ADD_PROC(ctx, inpN_tree, OID_AUTO, "enable",
+		    CTLFLAG_RW | CTLTYPE_UINT, &ti_adc_inputs[ain], 0,
+		    ti_adc_enable_proc, "IU", "Enable ADC input");
+		SYSCTL_ADD_PROC(ctx, inpN_tree, OID_AUTO, "open_delay",
+		    CTLFLAG_RW | CTLTYPE_UINT,  &ti_adc_inputs[ain], 0,
+		    ti_adc_open_delay_proc, "IU", "ADC open delay");
+		SYSCTL_ADD_PROC(ctx, inpN_tree, OID_AUTO, "samples_avg",
+		    CTLFLAG_RW | CTLTYPE_UINT,  &ti_adc_inputs[ain], 0,
+		    ti_adc_samples_avg_proc, "IU", "ADC samples average");
+		SYSCTL_ADD_INT(ctx, inpN_tree, OID_AUTO, "input",
+		    CTLFLAG_RD, &ti_adc_inputs[ain].value, 0,
+		    "Converted raw value for the ADC input");
+	}
+}
+
+static void
+ti_adc_inputs_init(struct ti_adc_softc *sc)
+{
+	int ain;
+	struct ti_adc_input *input;
+
+	TI_ADC_LOCK(sc);
+	for (ain = 0; ain < TI_ADC_NPINS; ain++) {
+		input = &ti_adc_inputs[ain];
+		input->sc = sc;
+		input->input = ain;
+		input->value = 0;
+		input->enable = 0;
+		input->samples = 0;
+		ti_adc_input_setup(sc, ain);
+	}
+	TI_ADC_UNLOCK(sc);
+}
+
+static void
+ti_adc_idlestep_init(struct ti_adc_softc *sc)
+{
+	uint32_t val;
+
+	val = ADC_READ4(sc, ADC_IDLECONFIG);
+
+	/* Set single ended operation. */
+	val &= ~ADC_STEP_DIFF_CNTRL;
+
+	/* Set the negative voltage reference. */
+	val &= ~ADC_STEP_RFM_MSK;
+	val |= ADC_STEP_RFM_VREFN << ADC_STEP_RFM_SHIFT;
+
+	/* Set the positive voltage reference. */
+	val &= ~ADC_STEP_RFP_MSK;
+	val |= ADC_STEP_RFP_VREFP << ADC_STEP_RFP_SHIFT;
+
+	/* Connect the input to VREFN. */
+	val &= ~ADC_STEP_INP_MSK;
+	val |= ADC_STEP_IN_VREFN << ADC_STEP_INP_SHIFT;
+
+	ADC_WRITE4(sc, ADC_IDLECONFIG, val);
+}
+
+static int
+ti_adc_probe(device_t dev)
+{
+
+	if (!ofw_bus_is_compatible(dev, "ti,adc"))
+		return (ENXIO);
+	device_set_desc(dev, "TI ADC controller");
+
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ti_adc_attach(device_t dev)
+{
+	int err, rid;
+	struct ti_adc_softc *sc;
+	uint32_t reg, rev;
+
+	sc = device_get_softc(dev);
+	sc->sc_dev = dev;
+
+	rid = 0;
+	sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
+	    RF_ACTIVE);
+	if (!sc->sc_mem_res) {
+		device_printf(dev, "cannot allocate memory window\n");
+		return (ENXIO);
+	}
+
+	rid = 0;
+	sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+	    RF_ACTIVE);
+	if (!sc->sc_irq_res) {
+		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
+		device_printf(dev, "cannot allocate interrupt\n");
+		return (ENXIO);
+	}
+
+	if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
+	    NULL, ti_adc_intr, sc, &sc->sc_intrhand) != 0) {
+		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
+		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
+		device_printf(dev, "Unable to setup the irq handler.\n");
+		return (ENXIO);
+	}
+
+	/* Activate the ADC_TSC module. */
+	err = ti_prcm_clk_enable(TSC_ADC_CLK);
+	if (err)
+		return (err);
+
+	/* Check the ADC revision. */
+	rev = ADC_READ4(sc, ADC_REVISION);
+	device_printf(dev,
+	    "scheme: %#x func: %#x rtl: %d rev: %d.%d custom rev: %d\n",
+	    (rev & ADC_REV_SCHEME_MSK) >> ADC_REV_SCHEME_SHIFT,
+	    (rev & ADC_REV_FUNC_MSK) >> ADC_REV_FUNC_SHIFT,
+	    (rev & ADC_REV_RTL_MSK) >> ADC_REV_RTL_SHIFT,
+	    (rev & ADC_REV_MAJOR_MSK) >> ADC_REV_MAJOR_SHIFT,
+	    rev & ADC_REV_MINOR_MSK,
+	    (rev & ADC_REV_CUSTOM_MSK) >> ADC_REV_CUSTOM_SHIFT);
+
+	/*
+	 * Disable the step write protect and make it store the step ID for
+	 * the captured data on FIFO.
+	 */
+	reg = ADC_READ4(sc, ADC_CTRL);
+	ADC_WRITE4(sc, ADC_CTRL, reg | ADC_CTRL_STEP_WP | ADC_CTRL_STEP_ID);
+
+	/*
+	 * Set the ADC prescaler to 2400 (yes, the actual value written here
+	 * is 2400 - 1).
+	 * This sets the ADC clock to ~10Khz (CLK_M_OSC / 2400).
+	 */
+	ADC_WRITE4(sc, ADC_CLKDIV, 2399);
+
+	TI_ADC_LOCK_INIT(sc);
+
+	ti_adc_idlestep_init(sc);
+	ti_adc_inputs_init(sc);
+	ti_adc_sysctl_init(sc);
+
+	return (0);
+}
+
+static int
+ti_adc_detach(device_t dev)
+{
+	struct ti_adc_softc *sc;
+
+	sc = device_get_softc(dev);
+
+	/* Turn off the ADC. */
+	TI_ADC_LOCK(sc);
+	ti_adc_reset(sc);
+	ti_adc_setup(sc);
+	TI_ADC_UNLOCK(sc);
+
+	TI_ADC_LOCK_DESTROY(sc);
+
+	if (sc->sc_intrhand)
+		bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_intrhand);
+	if (sc->sc_irq_res)
+		bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res);
+	if (sc->sc_mem_res)
+		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);
+
+	return (bus_generic_detach(dev));
+}
+
+static device_method_t ti_adc_methods[] = {
+	DEVMETHOD(device_probe,		ti_adc_probe),
+	DEVMETHOD(device_attach,	ti_adc_attach),
+	DEVMETHOD(device_detach,	ti_adc_detach),
+
+	DEVMETHOD_END
+};
+
+static driver_t ti_adc_driver = {
+	"ti_adc",
+	ti_adc_methods,
+	sizeof(struct ti_adc_softc),
+};
+
+static devclass_t ti_adc_devclass;
+
+DRIVER_MODULE(ti_adc, simplebus, ti_adc_driver, ti_adc_devclass, 0, 0);
+MODULE_VERSION(ti_adc, 1);
+MODULE_DEPEND(ti_adc, simplebus, 1, 1, 1);

Added: head/sys/arm/ti/ti_adcreg.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/arm/ti/ti_adcreg.h	Mon Mar 24 20:06:27 2014	(r263693)
@@ -0,0 +1,118 @@
+/*-
+ * Copyright 2014 Luiz Otavio O Souza <loos@freebsd.org>
+ * All rights reserved.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TI_ADCREG_H_
+#define _TI_ADCREG_H_
+
+#define	ADC_REVISION		0x000
+#define	ADC_REV_SCHEME_MSK		0xc0000000
+#define	ADC_REV_SCHEME_SHIFT		30
+#define	ADC_REV_FUNC_MSK		0x0fff0000
+#define	ADC_REV_FUNC_SHIFT		16
+#define	ADC_REV_RTL_MSK			0x0000f800
+#define	ADC_REV_RTL_SHIFT		11
+#define	ADC_REV_MAJOR_MSK		0x00000700
+#define	ADC_REV_MAJOR_SHIFT		8
+#define	ADC_REV_CUSTOM_MSK		0x000000c0
+#define	ADC_REV_CUSTOM_SHIFT		6
+#define	ADC_REV_MINOR_MSK		0x0000003f
+#define	ADC_SYSCFG		0x010
+#define	ADC_SYSCFG_IDLE_MSK		0x000000c0
+#define	ADC_SYSCFG_IDLE_SHIFT		2
+#define	ADC_IRQSTATUS_RAW	0x024
+#define	ADC_IRQSTATUS		0x028
+#define	ADC_IRQENABLE_SET	0x02c
+#define	ADC_IRQENABLE_CLR	0x030
+#define	ADC_IRQ_HW_PEN_SYNC		(1 << 10)
+#define	ADC_IRQ_PEN_UP			(1 << 9)
+#define	ADC_IRQ_OUT_RANGE		(1 << 8)
+#define	ADC_IRQ_FIFO1_UNDR		(1 << 7)
+#define	ADC_IRQ_FIFO1_OVERR		(1 << 6)
+#define	ADC_IRQ_FIFO1_THRES		(1 << 5)
+#define	ADC_IRQ_FIFO0_UNDR		(1 << 4)
+#define	ADC_IRQ_FIFO0_OVERR		(1 << 3)
+#define	ADC_IRQ_FIFO0_THRES		(1 << 2)
+#define	ADC_IRQ_END_OF_SEQ		(1 << 1)
+#define	ADC_IRQ_HW_PEN_ASYNC		(1 << 0)
+#define	ADC_CTRL		0x040
+#define	ADC_CTRL_STEP_WP		(1 << 2)
+#define	ADC_CTRL_STEP_ID		(1 << 1)
+#define	ADC_CTRL_ENABLE			(1 << 0)
+#define	ADC_STAT		0x044
+#define	ADC_CLKDIV		0x04c
+#define	ADC_STEPENABLE		0x054
+#define	ADC_IDLECONFIG		0x058
+#define	ADC_STEPCFG1		0x064
+#define	ADC_STEPDLY1		0x068
+#define	ADC_STEPCFG2		0x06c
+#define	ADC_STEPDLY2		0x070
+#define	ADC_STEPCFG3		0x074
+#define	ADC_STEPDLY3		0x078
+#define	ADC_STEPCFG4		0x07c
+#define	ADC_STEPDLY4		0x080
+#define	ADC_STEPCFG5		0x084
+#define	ADC_STEPDLY5		0x088
+#define	ADC_STEPCFG6		0x08c
+#define	ADC_STEPDLY6		0x090
+#define	ADC_STEPCFG7		0x094
+#define	ADC_STEPDLY7		0x098
+#define	ADC_STEP_DIFF_CNTRL		(1 << 25)
+#define	ADC_STEP_RFM_MSK		0x01800000
+#define	ADC_STEP_RFM_SHIFT		23
+#define	ADC_STEP_RFM_VSSA		0
+#define	ADC_STEP_RFM_XNUR		1
+#define	ADC_STEP_RFM_YNLR		2
+#define	ADC_STEP_RFM_VREFN		3
+#define	ADC_STEP_INP_MSK		0x00780000
+#define	ADC_STEP_INP_SHIFT		19
+#define	ADC_STEP_INM_MSK		0x00078000
+#define	ADC_STEP_INM_SHIFT		15
+#define	ADC_STEP_IN_VREFN		8
+#define	ADC_STEP_RFP_MSK		0x00007000
+#define	ADC_STEP_RFP_SHIFT		12
+#define	ADC_STEP_RFP_VDDA		0
+#define	ADC_STEP_RFP_XPUL		1
+#define	ADC_STEP_RFP_YPLL		2
+#define	ADC_STEP_RFP_VREFP		3
+#define	ADC_STEP_RFP_INTREF		4
+#define	ADC_STEP_AVG_MSK		0x0000001c
+#define	ADC_STEP_AVG_SHIFT		2
+#define	ADC_STEP_MODE_MSK		0x00000003
+#define	ADC_STEP_MODE_ONESHOT		0x00000000
+#define	ADC_STEP_MODE_CONTINUOUS	0x00000001
+#define	ADC_STEP_SAMPLE_DELAY		0xff000000
+#define	ADC_STEP_OPEN_DELAY		0x0003ffff
+#define	ADC_FIFO0COUNT		0x0e4
+#define	ADC_FIFO0THRESHOLD	0x0e8
+#define	ADC_FIFO0DATA		0x100
+#define	ADC_FIFO_COUNT_MSK		0x0000007f
+#define	ADC_FIFO_STEP_ID_MSK		0x000f0000
+#define	ADC_FIFO_STEP_ID_SHIFT		16
+#define	ADC_FIFO_DATA_MSK		0x00000fff
+
+#endif /* _TI_ADCREG_H_ */

Added: head/sys/arm/ti/ti_adcvar.h
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/arm/ti/ti_adcvar.h	Mon Mar 24 20:06:27 2014	(r263693)
@@ -0,0 +1,69 @@
+/*-
+ * Copyright 2014 Luiz Otavio O Souza <loos@freebsd.org>
+ * All rights reserved.
+ *
+ * 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 AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _TI_ADCVAR_H_
+#define _TI_ADCVAR_H_
+
+#define	TI_ADC_NPINS	7
+
+#define	ADC_READ4(_sc, reg)	bus_read_4((_sc)->sc_mem_res, reg)
+#define	ADC_WRITE4(_sc, reg, value)	\
+	bus_write_4((_sc)->sc_mem_res, reg, value)
+
+struct ti_adc_softc {
+	device_t		sc_dev;
+	int			sc_last_state;
+	struct mtx		sc_mtx;
+	struct resource		*sc_mem_res;
+	struct resource		*sc_irq_res;
+	void			*sc_intrhand;
+};
+
+struct ti_adc_input {
+	int32_t			enable;		/* input enabled */
+	int32_t			samples;	/* samples average */
+	int32_t			input;		/* input number */
+	int32_t			value;		/* raw converted value */
+	uint32_t		stepconfig;	/* step config register */
+	uint32_t		stepdelay;	/* step delay register */
+	struct ti_adc_softc	*sc;		/* pointer to adc softc */
+};
+
+#define	TI_ADC_LOCK(_sc)		\
+	mtx_lock(&(_sc)->sc_mtx)
+#define	TI_ADC_UNLOCK(_sc)		\
+	mtx_unlock(&(_sc)->sc_mtx)
+#define	TI_ADC_LOCK_INIT(_sc)	\
+	mtx_init(&_sc->sc_mtx, device_get_nameunit(_sc->sc_dev), \
+	    "ti_adc", MTX_DEF)
+#define	TI_ADC_LOCK_DESTROY(_sc)	\
+	mtx_destroy(&_sc->sc_mtx);
+#define	TI_ADC_LOCK_ASSERT(_sc)	\
+	mtx_assert(&(_sc)->sc_mtx, MA_OWNED)
+
+#endif /* _TI_ADCVAR_H_ */

Modified: head/sys/arm/ti/ti_prcm.h
==============================================================================
--- head/sys/arm/ti/ti_prcm.h	Mon Mar 24 19:21:14 2014	(r263692)
+++ head/sys/arm/ti/ti_prcm.h	Mon Mar 24 20:06:27 2014	(r263693)
@@ -162,6 +162,8 @@ typedef enum {
 
 	PRUSS_CLK = 1700,
 
+	TSC_ADC_CLK = 1800,
+
 	INVALID_CLK_IDENT
 
 } clk_ident_t;

Modified: head/sys/boot/fdt/dts/arm/am335x.dtsi
==============================================================================
--- head/sys/boot/fdt/dts/arm/am335x.dtsi	Mon Mar 24 19:21:14 2014	(r263692)
+++ head/sys/boot/fdt/dts/arm/am335x.dtsi	Mon Mar 24 20:06:27 2014	(r263693)
@@ -75,6 +75,13 @@
 			interrupt-parent = <&AINTC>;
 		};
 
+		adc0: adc@44E0D000 {
+			compatible = "ti,adc";
+			reg = <0x44E0D000 0x2000>;
+			interrupts = < 16 >;
+			interrupt-parent = <&AINTC>;
+ 		};
+ 		

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201403242006.s2OK6RVA070560>