drivers: spi: rv32m1: Add driver for RV32M1 LPSPI

Add SPI driver and bindings for LPSPI peripheral for the RV32M1 SOC.
Based heavily on the existing mcux LPSPI driver.

Signed-off-by: Karsten Koenig <karsten.koenig.030@gmail.com>
This commit is contained in:
Karsten Koenig 2019-08-07 17:13:35 +02:00 committed by Maureen Helm
commit ee2dd7322f
13 changed files with 545 additions and 1 deletions

View file

@ -162,6 +162,7 @@
/drivers/ptp_clock/ @jukkar /drivers/ptp_clock/ @jukkar
/drivers/spi/ @tbursztyka /drivers/spi/ @tbursztyka
/drivers/spi/spi_ll_stm32.* @superna9999 /drivers/spi/spi_ll_stm32.* @superna9999
/drivers/spi/spi_rv32m1_lpspi* @karstenkoenig
/drivers/timer/apic_timer.c @andrewboie /drivers/timer/apic_timer.c @andrewboie
/drivers/timer/cortex_m_systick.c @ioannisg /drivers/timer/cortex_m_systick.c @ioannisg
/drivers/timer/altera_avalon_timer_hal.c @wentongwu /drivers/timer/altera_avalon_timer_hal.c @wentongwu

View file

@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_LPSPI spi_mcux_lpspi.c)
zephyr_library_sources_ifdef(CONFIG_SPI_SAM spi_sam.c) zephyr_library_sources_ifdef(CONFIG_SPI_SAM spi_sam.c)
zephyr_library_sources_ifdef(CONFIG_SPI_SAM0 spi_sam0.c) zephyr_library_sources_ifdef(CONFIG_SPI_SAM0 spi_sam0.c)
zephyr_library_sources_ifdef(CONFIG_SPI_SIFIVE spi_sifive.c) zephyr_library_sources_ifdef(CONFIG_SPI_SIFIVE spi_sifive.c)
zephyr_library_sources_ifdef(CONFIG_SPI_RV32M1_LPSPI spi_rv32m1_lpspi.c)
zephyr_library_sources_ifdef(CONFIG_NRFX_SPI spi_nrfx_spi.c) zephyr_library_sources_ifdef(CONFIG_NRFX_SPI spi_nrfx_spi.c)
zephyr_library_sources_ifdef(CONFIG_NRFX_SPIM spi_nrfx_spim.c) zephyr_library_sources_ifdef(CONFIG_NRFX_SPIM spi_nrfx_spim.c)
zephyr_library_sources_ifdef(CONFIG_NRFX_SPIS spi_nrfx_spis.c) zephyr_library_sources_ifdef(CONFIG_NRFX_SPIS spi_nrfx_spis.c)

View file

@ -187,6 +187,8 @@ source "drivers/spi/Kconfig.mcux_dspi"
source "drivers/spi/Kconfig.mcux_lpspi" source "drivers/spi/Kconfig.mcux_lpspi"
source "drivers/spi/Kconfig.rv32m1_lpspi"
source "drivers/spi/Kconfig.sam" source "drivers/spi/Kconfig.sam"
source "drivers/spi/Kconfig.sam0" source "drivers/spi/Kconfig.sam0"

View file

@ -0,0 +1,12 @@
# Kconfig - RV32M1 SPI
#
# Copyright (c) 2019, Karsten Koenig <karsten.koenig.030@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
#
config SPI_RV32M1_LPSPI
bool "RV32M1 LPSPI driver"
depends on HAS_RV32M1_LPSPI && CLOCK_CONTROL
help
Enable the RV32M1 LPSPI driver.

View file

@ -0,0 +1,409 @@
/*
* Copyright (c) 2018, NXP
*
* Forked off the spi_mcux_lpi2c driver.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <drivers/spi.h>
#include <drivers/clock_control.h>
#include <fsl_lpspi.h>
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(spi_rv32m1_lpspi);
#include "spi_context.h"
#define CHIP_SELECT_COUNT 4
#define MAX_DATA_WIDTH 4096
struct spi_mcux_config {
LPSPI_Type *base;
char *clock_name;
clock_control_subsys_t clock_subsys;
clock_ip_name_t clock_ip_name;
u32_t clock_ip_src;
void (*irq_config_func)(struct device *dev);
};
struct spi_mcux_data {
lpspi_master_handle_t handle;
struct spi_context ctx;
size_t transfer_len;
};
static void spi_mcux_transfer_next_packet(struct device *dev)
{
const struct spi_mcux_config *config = dev->config->config_info;
struct spi_mcux_data *data = dev->driver_data;
LPSPI_Type *base = config->base;
struct spi_context *ctx = &data->ctx;
lpspi_transfer_t transfer;
status_t status;
if ((ctx->tx_len == 0) && (ctx->rx_len == 0)) {
/* nothing left to rx or tx, we're done! */
spi_context_cs_control(&data->ctx, false);
spi_context_complete(&data->ctx, 0);
return;
}
transfer.configFlags = kLPSPI_MasterPcsContinuous |
(ctx->config->slave << LPSPI_MASTER_PCS_SHIFT);
if (ctx->tx_len == 0) {
/* rx only, nothing to tx */
transfer.txData = NULL;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->rx_len;
} else if (ctx->rx_len == 0) {
/* tx only, nothing to rx */
transfer.txData = (u8_t *) ctx->tx_buf;
transfer.rxData = NULL;
transfer.dataSize = ctx->tx_len;
} else if (ctx->tx_len == ctx->rx_len) {
/* rx and tx are the same length */
transfer.txData = (u8_t *) ctx->tx_buf;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->tx_len;
} else if (ctx->tx_len > ctx->rx_len) {
/* Break up the tx into multiple transfers so we don't have to
* rx into a longer intermediate buffer. Leave chip select
* active between transfers.
*/
transfer.txData = (u8_t *) ctx->tx_buf;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->rx_len;
transfer.configFlags |= kLPSPI_MasterPcsContinuous;
} else {
/* Break up the rx into multiple transfers so we don't have to
* tx from a longer intermediate buffer. Leave chip select
* active between transfers.
*/
transfer.txData = (u8_t *) ctx->tx_buf;
transfer.rxData = ctx->rx_buf;
transfer.dataSize = ctx->tx_len;
transfer.configFlags |= kLPSPI_MasterPcsContinuous;
}
if (!(ctx->tx_count <= 1 && ctx->rx_count <= 1)) {
transfer.configFlags |= kLPSPI_MasterPcsContinuous;
}
data->transfer_len = transfer.dataSize;
status = LPSPI_MasterTransferNonBlocking(base, &data->handle,
&transfer);
if (status != kStatus_Success) {
LOG_ERR("Transfer could not start");
}
}
static void spi_mcux_isr(void *arg)
{
struct device *dev = (struct device *)arg;
const struct spi_mcux_config *config = dev->config->config_info;
struct spi_mcux_data *data = dev->driver_data;
LPSPI_Type *base = config->base;
LPSPI_MasterTransferHandleIRQ(base, &data->handle);
}
static void spi_mcux_master_transfer_callback(LPSPI_Type *base,
lpspi_master_handle_t *handle, status_t status, void *userData)
{
struct device *dev = userData;
struct spi_mcux_data *data = dev->driver_data;
spi_context_update_tx(&data->ctx, 1, data->transfer_len);
spi_context_update_rx(&data->ctx, 1, data->transfer_len);
spi_mcux_transfer_next_packet(dev);
}
static int spi_mcux_configure(struct device *dev,
const struct spi_config *spi_cfg)
{
const struct spi_mcux_config *config = dev->config->config_info;
struct spi_mcux_data *data = dev->driver_data;
LPSPI_Type *base = config->base;
lpspi_master_config_t master_config;
struct device *clock_dev;
u32_t clock_freq;
u32_t word_size;
if (spi_context_configured(&data->ctx, spi_cfg)) {
/* This configuration is already in use */
return 0;
}
LPSPI_MasterGetDefaultConfig(&master_config);
if (spi_cfg->slave > CHIP_SELECT_COUNT) {
LOG_ERR("Slave %d is greater than %d",
spi_cfg->slave,
CHIP_SELECT_COUNT);
return -EINVAL;
}
word_size = SPI_WORD_SIZE_GET(spi_cfg->operation);
if (word_size > MAX_DATA_WIDTH) {
LOG_ERR("Word size %d is greater than %d",
word_size, MAX_DATA_WIDTH);
return -EINVAL;
}
master_config.bitsPerFrame = word_size;
master_config.cpol =
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPOL)
? kLPSPI_ClockPolarityActiveLow
: kLPSPI_ClockPolarityActiveHigh;
master_config.cpha =
(SPI_MODE_GET(spi_cfg->operation) & SPI_MODE_CPHA)
? kLPSPI_ClockPhaseSecondEdge
: kLPSPI_ClockPhaseFirstEdge;
master_config.direction =
(spi_cfg->operation & SPI_TRANSFER_LSB)
? kLPSPI_LsbFirst
: kLPSPI_MsbFirst;
master_config.baudRate = spi_cfg->frequency;
clock_dev = device_get_binding(config->clock_name);
if (clock_dev == NULL) {
return -EINVAL;
}
if (clock_control_get_rate(clock_dev, config->clock_subsys,
&clock_freq)) {
return -EINVAL;
}
LPSPI_MasterInit(base, &master_config, clock_freq);
LPSPI_MasterTransferCreateHandle(base, &data->handle,
spi_mcux_master_transfer_callback, dev);
data->ctx.config = spi_cfg;
spi_context_cs_configure(&data->ctx);
return 0;
}
static int transceive(struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
bool asynchronous,
struct k_poll_signal *signal)
{
struct spi_mcux_data *data = dev->driver_data;
int ret;
spi_context_lock(&data->ctx, asynchronous, signal);
ret = spi_mcux_configure(dev, spi_cfg);
if (ret) {
goto out;
}
spi_context_buffers_setup(&data->ctx, tx_bufs, rx_bufs, 1);
spi_context_cs_control(&data->ctx, true);
spi_mcux_transfer_next_packet(dev);
ret = spi_context_wait_for_completion(&data->ctx);
out:
spi_context_release(&data->ctx, ret);
return ret;
}
static int spi_mcux_transceive(struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs)
{
return transceive(dev, spi_cfg, tx_bufs, rx_bufs, false, NULL);
}
#ifdef CONFIG_SPI_ASYNC
static int spi_mcux_transceive_async(struct device *dev,
const struct spi_config *spi_cfg,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs,
struct k_poll_signal *async)
{
return transceive(dev, spi_cfg, tx_bufs, rx_bufs, true, async);
}
#endif /* CONFIG_SPI_ASYNC */
static int spi_mcux_release(struct device *dev,
const struct spi_config *spi_cfg)
{
struct spi_mcux_data *data = dev->driver_data;
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
static int spi_mcux_init(struct device *dev)
{
const struct spi_mcux_config *config = dev->config->config_info;
struct spi_mcux_data *data = dev->driver_data;
CLOCK_SetIpSrc(config->clock_ip_name, config->clock_ip_src);
config->irq_config_func(dev);
spi_context_unlock_unconditionally(&data->ctx);
return 0;
}
static const struct spi_driver_api spi_mcux_driver_api = {
.transceive = spi_mcux_transceive,
#ifdef CONFIG_SPI_ASYNC
.transceive_async = spi_mcux_transceive_async,
#endif
.release = spi_mcux_release,
};
#ifdef CONFIG_SPI_0
static void spi_mcux_config_func_0(struct device *dev);
static const struct spi_mcux_config spi_mcux_config_0 = {
.base = (LPSPI_Type *) DT_OPENISA_RV32M1_LPSPI_SPI_0_BASE_ADDRESS,
.clock_name = DT_OPENISA_RV32M1_LPSPI_SPI_0_CLOCK_CONTROLLER,
.clock_subsys = (clock_control_subsys_t)
DT_OPENISA_RV32M1_LPSPI_SPI_0_CLOCK_NAME,
.irq_config_func = spi_mcux_config_func_0,
.clock_ip_name = kCLOCK_Lpspi0,
.clock_ip_src = kCLOCK_IpSrcFircAsync,
};
static struct spi_mcux_data spi_mcux_data_0 = {
SPI_CONTEXT_INIT_LOCK(spi_mcux_data_0, ctx),
SPI_CONTEXT_INIT_SYNC(spi_mcux_data_0, ctx),
};
DEVICE_AND_API_INIT(spi_mcux_0, DT_OPENISA_RV32M1_LPSPI_SPI_0_LABEL,
&spi_mcux_init, &spi_mcux_data_0, &spi_mcux_config_0,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&spi_mcux_driver_api);
static void spi_mcux_config_func_0(struct device *dev)
{
IRQ_CONNECT(DT_OPENISA_RV32M1_LPSPI_SPI_0_IRQ_0,
DT_OPENISA_RV32M1_LPSPI_SPI_0_IRQ_0_PRIORITY,
spi_mcux_isr, DEVICE_GET(spi_mcux_0), 0);
irq_enable(DT_OPENISA_RV32M1_LPSPI_SPI_0_IRQ_0);
}
#endif /* SPI_0 */
#ifdef CONFIG_SPI_1
static void spi_mcux_config_func_1(struct device *dev);
static const struct spi_mcux_config spi_mcux_config_1 = {
.base = (LPSPI_Type *) DT_OPENISA_RV32M1_LPSPI_SPI_1_BASE_ADDRESS,
.clock_name = DT_OPENISA_RV32M1_LPSPI_SPI_1_CLOCK_CONTROLLER,
.clock_subsys = (clock_control_subsys_t)
DT_OPENISA_RV32M1_LPSPI_SPI_1_CLOCK_NAME,
.irq_config_func = spi_mcux_config_func_1,
.clock_ip_name = kCLOCK_Lpspi1,
.clock_ip_src = kCLOCK_IpSrcFircAsync,
};
static struct spi_mcux_data spi_mcux_data_1 = {
SPI_CONTEXT_INIT_LOCK(spi_mcux_data_1, ctx),
SPI_CONTEXT_INIT_SYNC(spi_mcux_data_1, ctx),
};
DEVICE_AND_API_INIT(spi_mcux_1, DT_OPENISA_RV32M1_LPSPI_SPI_1_LABEL,
&spi_mcux_init, &spi_mcux_data_1, &spi_mcux_config_1,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&spi_mcux_driver_api);
static void spi_mcux_config_func_1(struct device *dev)
{
IRQ_CONNECT(DT_OPENISA_RV32M1_LPSPI_SPI_1_IRQ_0,
DT_OPENISA_RV32M1_LPSPI_SPI_1_IRQ_0_PRIORITY,
spi_mcux_isr, DEVICE_GET(spi_mcux_1), 0);
irq_enable(DT_OPENISA_RV32M1_LPSPI_SPI_1_IRQ_0);
}
#endif /* SPI_1 */
#ifdef CONFIG_SPI_2
static void spi_mcux_config_func_2(struct device *dev);
static const struct spi_mcux_config spi_mcux_config_2 = {
.base = (LPSPI_Type *) DT_OPENISA_RV32M1_LPSPI_SPI_2_BASE_ADDRESS,
.clock_name = DT_OPENISA_RV32M1_LPSPI_SPI_2_CLOCK_CONTROLLER,
.clock_subsys = (clock_control_subsys_t)
DT_OPENISA_RV32M1_LPSPI_SPI_2_CLOCK_NAME,
.irq_config_func = spi_mcux_config_func_2,
.clock_ip_name = kCLOCK_Lpspi2,
.clock_ip_src = kCLOCK_IpSrcFircAsync,
};
static struct spi_mcux_data spi_mcux_data_2 = {
SPI_CONTEXT_INIT_LOCK(spi_mcux_data_2, ctx),
SPI_CONTEXT_INIT_SYNC(spi_mcux_data_2, ctx),
};
DEVICE_AND_API_INIT(spi_mcux_2, DT_OPENISA_RV32M1_LPSPI_SPI_2_LABEL,
&spi_mcux_init, &spi_mcux_data_2, &spi_mcux_config_2,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&spi_mcux_driver_api);
static void spi_mcux_config_func_2(struct device *dev)
{
IRQ_CONNECT(DT_OPENISA_RV32M1_LPSPI_SPI_2_IRQ_0,
DT_OPENISA_RV32M1_LPSPI_SPI_2_IRQ_0_PRIORITY,
spi_mcux_isr, DEVICE_GET(spi_mcux_2), 0);
irq_enable(DT_OPENISA_RV32M1_LPSPI_SPI_2_IRQ_0);
}
#endif /* SPI_2 */
#ifdef CONFIG_SPI_3
static void spi_mcux_config_func_3(struct device *dev);
static const struct spi_mcux_config spi_mcux_config_3 = {
.base = (LPSPI_Type *) DT_OPENISA_RV32M1_LPSPI_SPI_3_BASE_ADDRESS,
.clock_name = DT_OPENISA_RV32M1_LPSPI_SPI_3_CLOCK_CONTROLLER,
.clock_subsys = (clock_control_subsys_t)
DT_OPENISA_RV32M1_LPSPI_SPI_3_CLOCK_NAME,
.irq_config_func = spi_mcux_config_func_3,
.clock_ip_name = kCLOCK_Lpspi3,
.clock_ip_src = kCLOCK_IpSrcFircAsync,
};
static struct spi_mcux_data spi_mcux_data_3 = {
SPI_CONTEXT_INIT_LOCK(spi_mcux_data_3, ctx),
SPI_CONTEXT_INIT_SYNC(spi_mcux_data_3, ctx),
};
DEVICE_AND_API_INIT(spi_mcux_3, DT_OPENISA_RV32M1_LPSPI_SPI_3_LABEL,
&spi_mcux_init, &spi_mcux_data_3, &spi_mcux_config_3,
POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE,
&spi_mcux_driver_api);
static void spi_mcux_config_func_3(struct device *dev)
{
IRQ_CONNECT(DT_OPENISA_RV32M1_LPSPI_SPI_3_IRQ_0,
DT_OPENISA_RV32M1_LPSPI_SPI_3_IRQ_0_PRIORITY,
spi_mcux_isr, DEVICE_GET(spi_mcux_3), 0);
irq_enable(DT_OPENISA_RV32M1_LPSPI_SPI_3_IRQ_0);
}
#endif /* CONFIG_SPI_3 */

View file

@ -0,0 +1,21 @@
#
# Copyright (c) 2019, Karsten Koenig <karsten.koenig.030@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
#
title: OpenISA LPSPI
description: >
This binding gives a base representation of the OpenISA LPSPI controller
compatible: "openisa,rv32m1-lpspi"
include: spi-controller.yaml
properties:
reg:
required: true
interrupts:
required: true

View file

@ -32,6 +32,10 @@
i2c-1 = &i2c1; i2c-1 = &i2c1;
i2c-2 = &i2c2; i2c-2 = &i2c2;
i2c-3 = &i2c3; i2c-3 = &i2c3;
spi-0 = &spi0;
spi-1 = &spi1;
spi-2 = &spi2;
spi-3 = &spi3;
}; };
cpus { cpus {
@ -443,6 +447,46 @@
status = "disabled"; status = "disabled";
}; };
spi0: spi@4003f000 {
compatible = "openisa,rv32m1-lpspi";
reg = <0x4003f000 0x78>;
label = "SPI_0";
status = "disabled";
clocks = <&pcc0 0xfc>;
#address-cells = <1>;
#size-cells = <0>;
};
spi1: spi@40040000 {
compatible = "openisa,rv32m1-lpspi";
reg = <0x40040000 0x78>;
label = "SPI_1";
status = "disabled";
clocks = <&pcc0 0x100>;
#address-cells = <1>;
#size-cells = <0>;
};
spi2: spi@40041000 {
compatible = "openisa,rv32m1-lpspi";
reg = <0x40041000 0x78>;
label = "SPI_2";
status = "disabled";
clocks = <&pcc0 0x104>;
#address-cells = <1>;
#size-cells = <0>;
};
spi3: spi@41035000 {
compatible = "openisa,rv32m1-lpspi";
reg = <0x41035000 0x78>;
label = "SPI_3";
status = "disabled";
clocks = <&pcc1 0xd4>;
#address-cells = <1>;
#size-cells = <0>;
};
flash-controller@40023000 { flash-controller@40023000 {
compatible = "openisa,rv32m1-ftfe"; compatible = "openisa,rv32m1-ftfe";
label = "FLASH_CTRL"; label = "FLASH_CTRL";

View file

@ -170,3 +170,23 @@
interrupt-parent = <&intmux0_ch1>; interrupt-parent = <&intmux0_ch1>;
interrupts = <24>; interrupts = <24>;
}; };
&spi0 {
interrupt-parent = <&event0>;
interrupts = <13>;
};
&spi1 {
interrupt-parent = <&event0>;
interrupts = <14>;
};
&spi2 {
interrupt-parent = <&intmux0_ch1>;
interrupts = <12>;
};
&spi3 {
interrupt-parent = <&intmux0_ch1>;
interrupts = <25>;
};

View file

@ -169,3 +169,23 @@
interrupt-parent = <&event1>; interrupt-parent = <&event1>;
interrupts = <16>; interrupts = <16>;
}; };
&spi0 {
interrupt-parent = <&intmux1_ch0>;
interrupts = <18>;
};
&spi1 {
interrupt-parent = <&intmux1_ch0>;
interrupts = <19>;
};
&spi2 {
interrupt-parent = <&intmux1_ch0>;
interrupts = <20>;
};
&spi3 {
interrupt-parent = <&event1>;
interrupts = <19>;
};

View file

@ -15,6 +15,11 @@ config HAS_RV32M1_LPI2C
help help
Set if the low power i2c (LPI2C) module is present in the SoC. Set if the low power i2c (LPI2C) module is present in the SoC.
config HAS_RV32M1_LPSPI
bool
help
Set if the low power spi (LPSPI) module is present in the SoC.
config HAS_RV32M1_FTFX config HAS_RV32M1_FTFX
bool bool
help help

View file

@ -161,6 +161,14 @@ config I2C_RV32M1_LPI2C
endif # I2C endif # I2C
if SPI
config SPI_RV32M1_LPSPI
default y
endif # SPI
if FLASH if FLASH
config SOC_FLASH_RV32M1 config SOC_FLASH_RV32M1

View file

@ -10,6 +10,7 @@ config SOC_OPENISA_RV32M1_RISCV32
select XIP select XIP
select HAS_RV32M1_LPUART select HAS_RV32M1_LPUART
select HAS_RV32M1_LPI2C select HAS_RV32M1_LPI2C
select HAS_RV32M1_LPSPI
select ATOMIC_OPERATIONS_C select ATOMIC_OPERATIONS_C
select VEGA_SDK_HAL select VEGA_SDK_HAL
select RISCV_SOC_INTERRUPT_INIT select RISCV_SOC_INTERRUPT_INIT

View file

@ -47,7 +47,7 @@ manifest:
revision: e0bfd6f253ca6a5e87b6f53a8bb317bd6f483511 revision: e0bfd6f253ca6a5e87b6f53a8bb317bd6f483511
path: modules/hal/nordic path: modules/hal/nordic
- name: hal_openisa - name: hal_openisa
revision: be5c01f86c96500def5079bcc58d2baefdffb6c8 revision: 0cbbf29f1523fe2970e57622bd79d9b115ca84e5
path: modules/hal/openisa path: modules/hal/openisa
- name: hal_microchip - name: hal_microchip
revision: 85302959c0c659311cf90ac51d133e5ce19c9288 revision: 85302959c0c659311cf90ac51d133e5ce19c9288