Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 2 Jul 2018 01:30:33 +0000 (UTC)
From:      Oleksandr Tymoshenko <gonzo@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r335855 - head/sys/arm/broadcom/bcm2835
Message-ID:  <201807020130.w621UXNw050537@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: gonzo
Date: Mon Jul  2 01:30:33 2018
New Revision: 335855
URL: https://svnweb.freebsd.org/changeset/base/335855

Log:
  [rpi] Add support for the second PWM channel
  
  Add support for the second channel to bcm2835_pwm driver. Configurable
  parameters like mode, period, ratio are exposed as sysctls with postfix '2',
  e.g.: dev.pwm.N.mode2, dev.pwm.N.period2, dev.pwm.N.ratio2
  
  Second channel can be enabled in DTB by configuring pwn-2chan overlay
  instead of pwm in config.txt. See [1]
  
  [1] https://github.com/raspberrypi/firmware/blob/master/boot/overlays/README
  
  Submitted by:	Bob Frazier
  Differential Revision:	https://reviews.freebsd.org/D15769

Modified:
  head/sys/arm/broadcom/bcm2835/bcm2835_pwm.c

Modified: head/sys/arm/broadcom/bcm2835/bcm2835_pwm.c
==============================================================================
--- head/sys/arm/broadcom/bcm2835/bcm2835_pwm.c	Sun Jul  1 22:48:00 2018	(r335854)
+++ head/sys/arm/broadcom/bcm2835/bcm2835_pwm.c	Mon Jul  2 01:30:33 2018	(r335855)
@@ -63,11 +63,13 @@ struct bcm_pwm_softc {
 
 	device_t		clkman;
 
-	uint32_t		freq;
-	uint32_t		period;
+	uint32_t		freq;    /* shared between channels 1 and 2 */
+	uint32_t		period;  /* channel 1 */
 	uint32_t		ratio;
 	uint32_t		mode;
-
+	uint32_t		period2; /* channel 2 */
+	uint32_t		ratio2;
+	uint32_t		mode2;
 };
 
 #define BCM_PWM_MEM_WRITE(_sc, _off, _val)		\
@@ -87,11 +89,15 @@ struct bcm_pwm_softc {
 #define R_RNG(_sc) BCM_PWM_MEM_READ(_sc, 0x10)
 #define W_DAT(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x14, _val)
 #define R_DAT(_sc) BCM_PWM_MEM_READ(_sc, 0x14)
+#define W_RNG2(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x20, _val)
+#define R_RNG2(_sc) BCM_PWM_MEM_READ(_sc, 0x20)
+#define W_DAT2(_sc, _val) BCM_PWM_MEM_WRITE(_sc, 0x24, _val)
+#define R_DAT2(_sc) BCM_PWM_MEM_READ(_sc, 0x24)
 
 static int
 bcm_pwm_reconf(struct bcm_pwm_softc *sc)
 {
-	uint32_t u;
+	uint32_t u, ctlr;
 
 	/* Disable PWM */
 	W_CTL(sc, 0);
@@ -99,26 +105,78 @@ bcm_pwm_reconf(struct bcm_pwm_softc *sc)
 	/* Stop PWM clock */
 	(void)bcm2835_clkman_set_frequency(sc->clkman, BCM_PWM_CLKSRC, 0);
 
-	if (sc->mode == 0)
-		return (0);
+	ctlr = 0; /* pre-assign zero, enable bits, write to CTL at end */
 
+	if (sc->mode == 0 && sc->mode2 == 0) /* both modes are zero */
+		return 0; /* device is now off - return */
+
+	/* set the PWM clock frequency */
+	/* TODO:  should I only do this if it changes and not stop it first? */
 	u = bcm2835_clkman_set_frequency(sc->clkman, BCM_PWM_CLKSRC, sc->freq);
 	if (u == 0)
 		return (EINVAL);
 	sc->freq = u;
 
-	/* Config PWM */
-	W_RNG(sc, sc->period);
-	if (sc->ratio > sc->period)
-		sc->ratio = sc->period;
-	W_DAT(sc, sc->ratio);
+	/* control register CTL bits:
+	 * (from BCM2835 ARM Peripherals manual, section 9.6)
+	 *
+	 * 15 MSEN2  chan 2 M/S enable; 0 for PWM algo, 1 for M/S transmission
+	 * 14 unused; always reads as 0
+	 * 13 USEF2  chan 2 use FIFO (0 uses data; 1 uses FIFO)
+	 * 12 POLA2  chan 2 invert polarity (0 normal, 1 inverted polarity)
+	 * 11 SBIT2  chan 2 'Silence' bit (when not transmitting data)
+	 * 10 RPTL2  chan 2 FIFO repeat last data (1 repeats, 0 interrupts)
+	 *  9 MODE2  chan 2 PWM/Serializer mode (0 PWM, 1 Serializer)
+	 *  8 PWEN2  chan 2 enable (0 disable, 1 enable)
+	 *  7 MSEN1  chan 1 M/S enable; 0 for PWM algo, 1 for M/S transmission
+	 *  6 CLRF1  chan 1 clear FIFO (set 1 to clear; always reads as 0)
+	 *  5 USEF1  chan 1 use FIFO (0 uses data; 1 uses FIFO)
+	 *  4 POLA1  chan 1 invert polarity (0 normal, 1 inverted polarity)
+	 *  3 SBIT1  chan 1 'Silence' bit (when not transmitting data)
+	 *  2 RTPL1  chan 1 FIFO repeat last data (1 repeats, 0 interrupts)
+	 *  1 MODE1  chan 1 PWM/Serializer mode (0 PWM, 1 Serializer)
+	 *  0 PWMEN1 chan 1 enable (0 disable, 1 enable)
+	 *
+	 * Notes on M/S enable:  when this bit is '1', a simple M/S ratio is used. In short,
+	 * the value of 'ratio' is the number of 'on' bits, and the total length of the data is
+	 * defined by 'period'.  So if 'ratio' is 2500 and 'period' is 10000, then the output
+	 * remains 'on' for 2500 clocks, and goes 'off' for the remaining 7500 clocks.
+	 * When the M/S enable is '0', a more complicated algorithm effectively 'dithers' the
+	 * pulses in order to obtain the desired ratio.  For details, see section 9.3 of the
+	 * BCM2835 ARM Peripherals manual.
+	 */
 
-	/* Start PWM */
-	if (sc->mode == 1)
-		W_CTL(sc, 0x81);
-	else
-		W_CTL(sc, 0x1);
+	if (sc->mode != 0) {
+		/* Config PWM Channel 1 */
+		W_RNG(sc, sc->period);
+		if (sc->ratio > sc->period)
+			sc->ratio = sc->period;
+		W_DAT(sc, sc->ratio);
 
+		/* Start PWM Channel 1 */
+		if (sc->mode == 1)
+			ctlr |= 0x81; /* chan 1 enable + chan 1 M/S enable */
+		else
+			ctlr |= 0x1; /* chan 1 enable */
+	}
+
+	if (sc->mode2 != 0) {
+		/* Config PWM Channel 2 */
+		W_RNG2(sc, sc->period2);
+		if (sc->ratio2 > sc->period2)
+			sc->ratio2 = sc->period2;
+		W_DAT2(sc, sc->ratio2);
+
+		/* Start PWM Channel 2 */
+		if (sc->mode2 == 1)
+			ctlr |= 0x8100; /* chan 2 enable + chan 2 M/S enable */
+		else
+			ctlr |= 0x100;  /* chan 2 enable */
+	}
+
+	/* write CTL register with updated value */
+	W_CTL(sc, ctlr);
+
 	return (0);
 }
 
@@ -138,7 +196,6 @@ bcm_pwm_pwm_freq_proc(SYSCTL_HANDLER_ARGS)
 	return (error);
 }
 
-
 static int
 bcm_pwm_mode_proc(SYSCTL_HANDLER_ARGS)
 {
@@ -203,11 +260,77 @@ bcm_pwm_ratio_proc(SYSCTL_HANDLER_ARGS)
 	if (r > sc->period)			// XXX >= ?
 		return (EINVAL);
 	sc->ratio = r;
-	BCM_PWM_MEM_WRITE(sc, 0x14, sc->ratio);
+	W_DAT(sc, sc->ratio);
 	return (0);
 }
 
 static int
+bcm_pwm_pwm_freq2_proc(SYSCTL_HANDLER_ARGS)
+{
+	struct bcm_pwm_softc *sc;
+	uint32_t r;
+	int error;
+
+	sc = (struct bcm_pwm_softc *)arg1;
+	if (sc->mode2 == 1)
+		r = sc->freq / sc->period2;
+	else
+		r = 0;
+	error = sysctl_handle_int(oidp, &r, sizeof(r), req);
+	return (error);
+}
+
+static int
+bcm_pwm_mode2_proc(SYSCTL_HANDLER_ARGS)
+{
+	struct bcm_pwm_softc *sc;
+	uint32_t r;
+	int error;
+
+	sc = (struct bcm_pwm_softc *)arg1;
+	r = sc->mode2;
+	error = sysctl_handle_int(oidp, &r, sizeof(r), req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+	if (r > 2)
+		return (EINVAL);
+	sc->mode2 = r;
+	return (bcm_pwm_reconf(sc));
+}
+
+static int
+bcm_pwm_period2_proc(SYSCTL_HANDLER_ARGS)
+{
+	struct bcm_pwm_softc *sc;
+	int error;
+
+	sc = (struct bcm_pwm_softc *)arg1;
+	error = sysctl_handle_int(oidp, &sc->period2, sizeof(sc->period2), req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+	return (bcm_pwm_reconf(sc));
+}
+
+static int
+bcm_pwm_ratio2_proc(SYSCTL_HANDLER_ARGS)
+{
+	struct bcm_pwm_softc *sc;
+	uint32_t r;
+	int error;
+
+	sc = (struct bcm_pwm_softc *)arg1;
+	r = sc->ratio2;
+	error = sysctl_handle_int(oidp, &r, sizeof(r), req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+	if (r > sc->period2)		// XXX >= ?
+		return (EINVAL);
+	sc->ratio2 = r;
+	W_DAT(sc, sc->ratio2);
+	return (0);
+}
+
+static int
 bcm_pwm_reg_proc(SYSCTL_HANDLER_ARGS)
 {
 	struct bcm_pwm_softc *sc;
@@ -257,19 +380,31 @@ bcm_pwm_sysctl_init(struct bcm_pwm_softc *sc)
 
 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "pwm_freq",
 	    CTLFLAG_RD | CTLTYPE_UINT, sc, 0,
-	    bcm_pwm_pwm_freq_proc, "IU", "PWM frequency (Hz)");
+	    bcm_pwm_pwm_freq_proc, "IU", "PWM frequency ch 1 (Hz)");
 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "period",
 	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
-	    bcm_pwm_period_proc, "IU", "PWM period (#clocks)");
+	    bcm_pwm_period_proc, "IU", "PWM period ch 1 (#clocks)");
 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "ratio",
 	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
-	    bcm_pwm_ratio_proc, "IU", "PWM ratio (0...period)");
+	    bcm_pwm_ratio_proc, "IU", "PWM ratio ch 1 (0...period)");
 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "freq",
 	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
 	    bcm_pwm_freq_proc, "IU", "PWM clock (Hz)");
 	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "mode",
 	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
-	    bcm_pwm_mode_proc, "IU", "PWM mode (0=off, 1=pwm, 2=dither)");
+	    bcm_pwm_mode_proc, "IU", "PWM mode ch 1 (0=off, 1=pwm, 2=dither)");
+	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "pwm_freq2",
+	    CTLFLAG_RD | CTLTYPE_UINT, sc, 0,
+	    bcm_pwm_pwm_freq2_proc, "IU", "PWM frequency ch 2 (Hz)");
+	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "period2",
+	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
+	    bcm_pwm_period2_proc, "IU", "PWM period ch 2 (#clocks)");
+	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "ratio2",
+	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
+	    bcm_pwm_ratio2_proc, "IU", "PWM ratio ch 2 (0...period)");
+	SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "mode2",
+	    CTLFLAG_RW | CTLTYPE_UINT, sc, 0,
+	    bcm_pwm_mode2_proc, "IU", "PWM mode ch 2 (0=off, 1=pwm, 2=dither)");
 }
 
 static int
@@ -321,11 +456,12 @@ bcm_pwm_attach(device_t dev)
 	/* Add sysctl nodes. */
 	bcm_pwm_sysctl_init(sc);
 
-	sc->freq = 125000000;
-	sc->period = 10000;
-	sc->ratio = 2500;
+	sc->freq = 125000000; /* 125 Mhz */
+	sc->period = 10000;   /* 12.5 khz */
+	sc->ratio = 2500;     /* 25% */
+	sc->period2 = 10000;  /* 12.5 khz */
+	sc->ratio2 = 2500;    /* 25% */
 
-
 	return (bus_generic_attach(dev));
 }
 
@@ -338,6 +474,7 @@ bcm_pwm_detach(device_t dev)
 
 	sc = device_get_softc(dev);
 	sc->mode = 0;
+	sc->mode2 = 0;
 	(void)bcm_pwm_reconf(sc);
 	if (sc->sc_mem_res)
 		bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res);



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