From: Melissa LeBlanc-Williams Date: Tue, 9 Mar 2021 18:19:34 +0000 (-0800) Subject: Merge pull request #429 from thomoray/add-rockpi4c-support X-Git-Tag: 6.4.0 X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/commitdiff_plain/e74f14561a7a76f22a72cbe00e3bb8c5e7b0b964?hp=40c98f0d33b69c0ed14fec6cf1868a50603a09c1 Merge pull request #429 from thomoray/add-rockpi4c-support Add rockpi4c support --- diff --git a/src/adafruit_blinka/board/radxa/rockpi4.py b/src/adafruit_blinka/board/radxa/rockpi4.py new file mode 100644 index 0000000..e327ff3 --- /dev/null +++ b/src/adafruit_blinka/board/radxa/rockpi4.py @@ -0,0 +1,62 @@ +"""Pin definitions for the Rock Pi 4.""" + +from adafruit_blinka.microcontroller.rockchip.rk3399 import pin + +D3 = pin.GPIO2_A7 # /I2C7_SDA/PIN 71/ +D5 = pin.GPIO2_B0 # /I2C7_SCL/PIN 72/ +D7 = pin.GPIO2_B3 # /SPI2_CLK/PIN 75/ +D8 = pin.GPIO4_C4 # /UART2_TXD/PIN 148/ +D10 = pin.GPIO4_C3 # /UART2_RXD/PIN 147/ +D11 = pin.GPIO4_C2 # /PWM0/PIN 146/ +D13 = pin.GPIO4_C6 # /PWM1/PIN 150/ +D15 = pin.GPIO4_C5 # /SPDIF_TX/PIN 149/ +D16 = pin.GPIO4_D2 # /PIN 154/ +D17 = pin.GPIO4_D4 # /PIN 156/ +D19 = pin.GPIO1_B0 # /UART4_TXD/SPI1_TXD/PIN 40/ +D21 = pin.GPIO1_A7 # /UART4_RXD/SPI1_RXD/PIN 39/ +D22 = pin.GPIO4_D5 # /PIN 157/ +D23 = pin.GPIO1_B1 # /SPI1_CLK/PIN 41/ +D24 = pin.GPIO1_B2 # /SPI1_CS/PIN 42/ +D27 = pin.GPIO2_A0 # /I2C2_SDA/PIN 64/ +D28 = pin.GPIO2_A1 # /I2C2_SCL/PIN 65/ +D29 = pin.GPIO2_B2 # /I2C6_SCL/SPI2_TXD/PIN 74/ +D31 = pin.GPIO2_B1 # /I2C6_SDA/SPI2_RXD/PIN 73/ +D32 = pin.GPIO3_C0 # /SPDIF_TX/UART3_CTS/PIN 112/ +D33 = pin.GPIO2_B4 # /SPI2_CS/PIN 76/ +D35 = pin.GPIO4_A5 # /I2S1_LRCK_TX/PIN 133/ +D36 = pin.GPIO4_A4 # /I2S1_LRCK_RX/PIN 132/ +D37 = pin.GPIO4_D6 # /PIN 158/ +D38 = pin.GPIO4_A6 # /I2S1_SDI/PIN 134/ +D40 = pin.GPIO4_A7 # /I2S1_SDO/PIN 135/ + +SDA2 = D27 +SCL2 = D28 + +SDA6 = D31 +SCL6 = D29 + +SDA7 = D3 +SCL7 = D5 + +SDA = SDA2 +SCL = SCL2 + +SCLK = D7 +MOSI = D29 +MISO = D31 +CS = D33 +SCK = SCLK + +UART2_TX = D8 +UART2_RX = D10 + +UART4_TX = D19 +UART4_RX = D21 + +UART_TX = UART2_TX +UART_RX = UART2_RX + +PWM0 = pin.PWM0 +PWM1 = pin.PWM1 + +ADC_IN0 = pin.ADC_IN0 diff --git a/src/adafruit_blinka/microcontroller/rockchip/PWMOut.py b/src/adafruit_blinka/microcontroller/rockchip/PWMOut.py new file mode 100644 index 0000000..09c4a8e --- /dev/null +++ b/src/adafruit_blinka/microcontroller/rockchip/PWMOut.py @@ -0,0 +1,392 @@ +""" +Much code from https://github.com/vsergeev/python-periphery/blob/master/periphery/pwm.py +Copyright (c) 2015-2016 vsergeev / Ivan (Vanya) A. Sergeev +License: MIT +""" + +import os +from time import sleep +from errno import EACCES + +try: + from microcontroller.pin import pwmOuts +except ImportError: + raise RuntimeError("No PWM outputs defined for this board.") from ImportError + + +# pylint: disable=unnecessary-pass, too-many-instance-attributes + + +class PWMError(IOError): + """Base class for PWM errors.""" + + pass + + +# pylint: enable=unnecessary-pass + + +class PWMOut: + """Pulse Width Modulation Output Class""" + + # Number of retries to check for successful PWM export on open + PWM_STAT_RETRIES = 10 + # Delay between check for successful PWM export on open (100ms) + PWM_STAT_DELAY = 0.1 + + # Sysfs paths + _chip_path = "pwmchip{}" + _channel_path = "pwm{}" + + def __init__(self, pwm, *, frequency=500, duty_cycle=0, variable_frequency=False): + """Instantiate a PWM object and open the sysfs PWM corresponding to the + specified chip and channel. + Args: + pwm (str): PWM pin. + frequency (int, float): target frequency in Hertz (32-bit). + duty_cycle (int, float): The fraction of each pulse which is high (16-bit). + variable_frequency (bool): True if the frequency will change over time. + Returns: + PWM: PWM object. + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if `chip` or `channel` types are invalid. + LookupError: if PWM chip does not exist. + TimeoutError: if waiting for PWM export times out. + """ + + self._chip = None + self._channel = None + self._period_ns = 0 + self._open(pwm, frequency, duty_cycle, variable_frequency) + + def __del__(self): + self.close() + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() + + def _open(self, pwm, frequency, duty_cycle, variable_frequency): + for pwmout in pwmOuts: + if pwmout[1] == pwm: + self._chip = pwmout[0][0] + self._channel = pwmout[0][1] + + self._chip_path = os.path.join( + "/sys/class/pwm", self._chip_path.format(self._chip) + ) + self._channel_path = os.path.join( + self._chip_path, self._channel_path.format(self._channel) + ) + + if variable_frequency: + print("Variable Frequency is not supported, continuing without it...") + + if not os.path.isdir(self._chip_path): + raise LookupError("Opening PWM: PWM chip {} not found.".format(self._chip)) + + if not os.path.isdir(self._channel_path): + # Exporting the PWM. + try: + with open(os.path.join(self._chip_path, "export"), "w") as f_export: + f_export.write("{:d}\n".format(self._channel)) + except IOError as e: + raise PWMError( + e.errno, "Exporting PWM channel: " + e.strerror + ) from IOError + + # Loop until PWM is exported + exported = False + for i in range(PWMOut.PWM_STAT_RETRIES): + if os.path.isdir(self._channel_path): + exported = True + break + + sleep(PWMOut.PWM_STAT_DELAY) + + if not exported: + raise TimeoutError( + 'Exporting PWM: waiting for "{:s}" timed out.'.format( + self._channel_path + ) + ) + + # Loop until 'period' is writable, This could take some time after + # export as application of the udev rules after export is asynchronous. + # Without this loop, the following properties may not be writable yet. + for i in range(PWMOut.PWM_STAT_RETRIES): + try: + with open( + os.path.join(self._channel_path, "period"), + "w", + ): + break + except IOError as e: + if e.errno != EACCES or ( + e.errno == EACCES and i == PWMOut.PWM_STAT_RETRIES - 1 + ): + raise PWMError( + e.errno, "Opening PWM period: " + e.strerror + ) from IOError + + sleep(PWMOut.PWM_STAT_DELAY) + + self.frequency = frequency + self.duty_cycle = duty_cycle + + # Cache the period for fast duty cycle updates + self._period_ns = self._get_period_ns() + + def close(self): + """Close the PWM.""" + if self._channel is not None: + # Unexporting the PWM channel + try: + unexport_fd = os.open( + os.path.join(self._chip_path, "unexport"), os.O_WRONLY + ) + os.write(unexport_fd, "{:d}\n".format(self._channel).encode()) + os.close(unexport_fd) + except OSError as e: + raise PWMError(e.errno, "Unexporting PWM: " + e.strerror) from OSError + + self._chip = None + self._channel = None + + def _write_channel_attr(self, attr, value): + with open(os.path.join(self._channel_path, attr), "w") as f_attr: + f_attr.write(value + "\n") + + def _read_channel_attr(self, attr): + with open(os.path.join(self._channel_path, attr), "r") as f_attr: + return f_attr.read().strip() + + # Methods + + def enable(self): + """Enable the PWM output.""" + self.enabled = True + + def disable(self): + """Disable the PWM output.""" + self.enabled = False + + # Mutable properties + + def _get_period(self): + return float(self.period_ms) / 1000 + + def _set_period(self, period): + if not isinstance(period, (int, float)): + raise TypeError("Invalid period type, should be int.") + + self.period_ms = int(period * 1000) + + period = property(_get_period, _set_period) + """Get or set the PWM's output period in seconds. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int. + + :type: int, float + """ + + def _get_period_ms(self): + return self.period_us / 1000 + + def _set_period_ms(self, period_ms): + if not isinstance(period_ms, (int, float)): + raise TypeError("Invalid period type, should be int or float.") + self.period_us = int(period_ms * 1000) + + period_ms = property(_get_period_ms, _set_period_ms) + """Get or set the PWM's output period in milliseconds. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int. + + :type: int, float + """ + + def _get_period_us(self): + return self.period_ns / 1000 + + def _set_period_us(self, period_us): + if not isinstance(period_us, int): + raise TypeError("Invalid period type, should be int.") + + self.period_ns = int(period_us * 1000) + + period_us = property(_get_period_us, _set_period_us) + """Get or set the PWM's output period in microseconds. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int. + + :type: int + """ + + def _get_period_ns(self): + period_ns = self._read_channel_attr("period") + try: + period_ns = int(period_ns) + except ValueError: + raise PWMError( + None, 'Unknown period value: "%s".' % period_ns + ) from ValueError + + self._period_ns = period_ns + + return period_ns + + def _set_period_ns(self, period_ns): + if not isinstance(period_ns, int): + raise TypeError("Invalid period type, should be int.") + + self._write_channel_attr("period", str(period_ns)) + + # Update our cached period + self._period_ns = period_ns + + period_ns = property(_get_period_ns, _set_period_ns) + """Get or set the PWM's output period in nanoseconds. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int. + + :type: int + """ + + def _get_duty_cycle_ns(self): + duty_cycle_ns_str = self._read_channel_attr("duty_cycle") + + try: + duty_cycle_ns = int(duty_cycle_ns_str) + except ValueError: + raise PWMError( + None, 'Unknown duty cycle value: "{:s}"'.format(duty_cycle_ns_str) + ) from ValueError + + return duty_cycle_ns + + def _set_duty_cycle_ns(self, duty_cycle_ns): + if not isinstance(duty_cycle_ns, int): + raise TypeError("Invalid duty cycle type, should be int.") + + self._write_channel_attr("duty_cycle", str(duty_cycle_ns)) + + duty_cycle_ns = property(_get_duty_cycle_ns, _set_duty_cycle_ns) + """Get or set the PWM's output duty cycle in nanoseconds. + + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int. + + :type: int + """ + + def _get_duty_cycle(self): + return float(self.duty_cycle_ns) / self._period_ns + + def _set_duty_cycle(self, duty_cycle): + if not isinstance(duty_cycle, (int, float)): + raise TypeError("Invalid duty cycle type, should be int or float.") + + if not 0.0 <= duty_cycle <= 1.0: + raise ValueError("Invalid duty cycle value, should be between 0.0 and 1.0.") + + # Convert duty cycle from ratio to nanoseconds + self.duty_cycle_ns = int(duty_cycle * self._period_ns) + + duty_cycle = property(_get_duty_cycle, _set_duty_cycle) + """Get or set the PWM's output duty cycle as a ratio from 0.0 to 1.0. + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int or float. + ValueError: if value is out of bounds of 0.0 to 1.0. + :type: int, float + """ + + def _get_frequency(self): + return 1.0 / self.period + + def _set_frequency(self, frequency): + if not isinstance(frequency, (int, float)): + raise TypeError("Invalid frequency type, should be int or float.") + + self.period = 1.0 / frequency + + frequency = property(_get_frequency, _set_frequency) + """Get or set the PWM's output frequency in Hertz. + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not int or float. + :type: int, float + """ + + def _get_polarity(self): + return self._read_channel_attr("polarity") + + def _set_polarity(self, polarity): + if not isinstance(polarity, str): + raise TypeError("Invalid polarity type, should be str.") + + if polarity.lower() not in ["normal", "inversed"]: + raise ValueError('Invalid polarity, can be: "normal" or "inversed".') + + self._write_channel_attr("polarity", polarity.lower()) + + polarity = property(_get_polarity, _set_polarity) + """Get or set the PWM's output polarity. Can be "normal" or "inversed". + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not str. + ValueError: if value is invalid. + :type: str + """ + + def _get_enabled(self): + enabled = self._read_channel_attr("enable") + + if enabled == "1": + return True + if enabled == "0": + return False + + raise PWMError(None, 'Unknown enabled value: "{:s}"'.format(enabled)) + + def _set_enabled(self, value): + if not isinstance(value, bool): + raise TypeError("Invalid enabled type, should be bool.") + + self._write_channel_attr("enable", "1" if value else "0") + + enabled = property(_get_enabled, _set_enabled) + """Get or set the PWM's output enabled state. + Raises: + PWMError: if an I/O or OS error occurs. + TypeError: if value type is not bool. + :type: bool + """ + + # String representation + + def __str__(self): + return ( + "PWM {:d}, chip {:d} (period={:f} sec, duty_cycle={:f}%," + " polarity={:s}, enabled={:s})".format( + self._channel, + self._chip, + self.period, + self.duty_cycle * 100, + self.polarity, + str(self.enabled), + ) + ) diff --git a/src/adafruit_blinka/microcontroller/rockchip/rk3399/__init__.py b/src/adafruit_blinka/microcontroller/rockchip/rk3399/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/adafruit_blinka/microcontroller/rockchip/rk3399/pin.py b/src/adafruit_blinka/microcontroller/rockchip/rk3399/pin.py new file mode 100644 index 0000000..4419861 --- /dev/null +++ b/src/adafruit_blinka/microcontroller/rockchip/rk3399/pin.py @@ -0,0 +1,77 @@ +"""A Pin class for use with Rockchip RK3399.""" + +from adafruit_blinka.microcontroller.generic_linux.sysfs_pin import Pin + +GPIO1_A7 = Pin(39) +GPIO1_B0 = Pin(40) +GPIO1_B1 = Pin(41) +GPIO1_B2 = Pin(42) +GPIO2_A0 = Pin(64) +GPIO2_A1 = Pin(65) +GPIO2_A7 = Pin(71) +GPIO2_B0 = Pin(72) +GPIO2_B1 = Pin(73) +GPIO2_B2 = Pin(74) +GPIO2_B3 = Pin(75) +GPIO2_B4 = Pin(76) +GPIO3_C0 = Pin(112) +GPIO4_A3 = Pin(131) +GPIO4_A4 = Pin(132) +GPIO4_A5 = Pin(133) +GPIO4_A6 = Pin(134) +GPIO4_A7 = Pin(135) +GPIO4_C2 = Pin(146) +GPIO4_C3 = Pin(147) +GPIO4_C4 = Pin(148) +GPIO4_C5 = Pin(149) +GPIO4_C6 = Pin(150) +GPIO4_D2 = Pin(154) +GPIO4_D4 = Pin(156) +GPIO4_D5 = Pin(157) +GPIO4_D6 = Pin(158) +ADC_IN0 = 1 + +# I2C +I2C2_SDA = GPIO2_A0 +I2C2_SCL = GPIO2_A1 +I2C6_SDA = GPIO2_B1 +I2C6_SCL = GPIO2_B2 +I2C7_SDA = GPIO2_A7 +I2C7_SCL = GPIO2_B0 + +# SPI +SPI1_CS = GPIO1_B2 +SPI1_SCLK = GPIO1_B1 +SPI1_MISO = GPIO1_B0 +SPI1_MOSI = GPIO1_A7 +SPI2_CS = GPIO2_B4 +SPI2_SCLK = GPIO2_A1 +SPI2_MISO = GPIO2_B1 +SPI2_MOSI = GPIO2_B2 + +# UART +UART0_TX = GPIO4_C4 +UART0_RX = GPIO4_C3 + +# PWM +PWM0 = GPIO4_C2 +PWM1 = GPIO4_C6 + +# ordered as i2cId, SCL, SDA +i2cPorts = ( + (0, I2C2_SCL, I2C2_SDA), + (1, I2C6_SCL, I2C6_SDA), + (7, I2C7_SCL, I2C7_SDA), +) + +# ordered as spiId, sckId, mosiId, misoId +spiPorts = ((1, SPI1_SCLK, SPI1_MOSI, SPI1_MISO),) + +# SysFS pwm outputs, pwm channel and pin in first tuple +pwmOuts = ( + ((0, 0), PWM0), + ((1, 0), PWM1), +) + +# SysFS analog inputs, Ordered as analog analogInId, device, and channel +analogIns = ((ADC_IN0, 0, 0),) diff --git a/src/analogio.py b/src/analogio.py index 8ca58c1..1db939a 100644 --- a/src/analogio.py +++ b/src/analogio.py @@ -21,6 +21,8 @@ elif detector.board.greatfet_one: from adafruit_blinka.microcontroller.nxp_lpc4330.analogio import AnalogOut elif detector.chip.RK3308: from adafruit_blinka.microcontroller.generic_linux.sysfs_analogin import AnalogIn +elif detector.chip.RK3399: + from adafruit_blinka.microcontroller.generic_linux.sysfs_analogin import AnalogIn elif detector.chip.IMX6ULL: from adafruit_blinka.microcontroller.generic_linux.sysfs_analogin import AnalogIn elif "sphinx" in sys.modules: diff --git a/src/board.py b/src/board.py index cf2076a..e4909f2 100755 --- a/src/board.py +++ b/src/board.py @@ -191,6 +191,9 @@ elif board_id == ap_board.ONION_OMEGA2: elif board_id == ap_board.ROCK_PI_S: from adafruit_blinka.board.radxa.rockpis import * +elif board_id == ap_board.ROCK_PI_4: + from adafruit_blinka.board.radxa.rockpi4 import * + elif board_id == ap_board.UDOO_X86: from adafruit_blinka.board.udoo_x86ultra import * diff --git a/src/busio.py b/src/busio.py index d4613a3..6749459 100755 --- a/src/busio.py +++ b/src/busio.py @@ -254,6 +254,9 @@ class SPI(Lockable): elif detector.board.ROCK_PI_S: from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI from adafruit_blinka.microcontroller.rockchip.rk3308.pin import Pin + elif detector.board.ROCK_PI_4: + from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI + from adafruit_blinka.microcontroller.rockchip.rk3399.pin import Pin elif detector.board.SIFIVE_UNLEASHED: from adafruit_blinka.microcontroller.generic_linux.spi import SPI as _SPI from adafruit_blinka.microcontroller.hfu540.pin import Pin diff --git a/src/digitalio.py b/src/digitalio.py index 08d1549..a59866b 100755 --- a/src/digitalio.py +++ b/src/digitalio.py @@ -51,6 +51,8 @@ elif detector.chip.MIPS24KEC: from adafruit_blinka.microcontroller.mips24kec.pin import Pin elif detector.chip.RK3308: from adafruit_blinka.microcontroller.rockchip.rk3308.pin import Pin +elif detector.chip.RK3399: + from adafruit_blinka.microcontroller.rockchip.rk3399.pin import Pin elif detector.board.ftdi_ft232h: from adafruit_blinka.microcontroller.ft232h.pin import Pin elif detector.board.binho_nova: diff --git a/src/microcontroller/__init__.py b/src/microcontroller/__init__.py index a5b3b75..abd5a10 100755 --- a/src/microcontroller/__init__.py +++ b/src/microcontroller/__init__.py @@ -75,6 +75,8 @@ elif chip_id == ap_chip.A33: from adafruit_blinka.microcontroller.allwinner.a33.pin import * elif chip_id == ap_chip.RK3308: from adafruit_blinka.microcontroller.rockchip.rk3308.pin import * +elif chip_id == ap_chip.RK3399: + from adafruit_blinka.microcontroller.rockchip.rk3399.pin import * elif chip_id == ap_chip.H5: from adafruit_blinka.microcontroller.allwinner.h5.pin import * elif chip_id == ap_chip.IMX8MX: diff --git a/src/microcontroller/pin.py b/src/microcontroller/pin.py index 4ac63dd..f83162f 100755 --- a/src/microcontroller/pin.py +++ b/src/microcontroller/pin.py @@ -56,6 +56,8 @@ elif chip_id == ap_chip.A33: from adafruit_blinka.microcontroller.allwinner.a33.pin import * elif chip_id == ap_chip.RK3308: from adafruit_blinka.microcontroller.rockchip.rk3308.pin import * +elif chip_id == ap_chip.RK3399: + from adafruit_blinka.microcontroller.rockchip.rk3399.pin import * elif chip_id == ap_chip.MIPS24KC: from adafruit_blinka.microcontroller.atheros.ar9331.pin import * elif chip_id == ap_chip.MIPS24KEC: diff --git a/src/pwmio.py b/src/pwmio.py index 05263c2..3a6b5bd 100644 --- a/src/pwmio.py +++ b/src/pwmio.py @@ -22,7 +22,7 @@ elif detector.board.any_giant_board: elif detector.board.any_beaglebone: from adafruit_blinka.microcontroller.am335x.sysfs_pwmout import PWMOut elif detector.board.any_rock_pi_board: - from adafruit_blinka.microcontroller.generic_linux.sysfs_pwmout import PWMOut + from adafruit_blinka.microcontroller.rockchip.PWMOut import PWMOut elif detector.board.binho_nova: from adafruit_blinka.microcontroller.nova.pwmout import PWMOut elif detector.board.greatfet_one: