Linux

PWM, LCD, and CTP from Kernel on STM32MP135

Published 20 Feb 2026. By Jakob Kastelic.

This is Part 10 in the series: Linux on STM32MP135. See other articles.

Backlight with GPIO

The display backlight is controlled via a single GPIO:

panel_backlight: panel-backlight {
	compatible = "gpio-backlight";
	gpios = <&gpiob 15 GPIO_ACTIVE_HIGH>;
	default-on;
	default-brightness-level = <1>;
	status = "okay";
};

With that, the backlight can be turned on and off as follows:

# echo 1 > /sys/class/backlight/panel-backlight/brightness
# echo 0 > /sys/class/backlight/panel-backlight/brightness

There’s no tricks to this, so long as we make sure that the GPIO mentioned in the DTS matches the one on the circuit schematic.

Backlight and PWM

To control the brightness, we need to enable the PWM corresponding to this GPIO pin and make sure the panel backlight is controlled by this PWM.

We have already implemented this previously in bare-metal using the HAL driver. We can check the TIM1, GPIOB, and GPIOC registers to see what is the effect of our configuration. In the following table, “Before” and “After” are on bare metal before and after configuring the PWM output.

RegisterAddressBeforeAfter
TIM1_CR10x440000000x000000000x00000001
TIM1_SR0x440000100x000000000x0003001f
TIM1_CCMR20x4400001c0x000000000x00000068
TIM1_CCER0x440000200x000000000x00000400
TIM1_CNT0x440000240x000000000x000001eb
TIM1_PSC0x440000280x000000000x00000063
TIM1_ARR0x4400002c0x000000000x000003e7
TIM1_CCR30x4400003c0x000000000x000001f4
TIM1_BDTR0x440000440x000000000x00008000
TIM1_DMAR0x4400004c0x000000000x00000001
TIM1_AF10x440000600x000000000x00000001
TIM1_AF20x440000640x000000000x00000001
TIM1_VERR0x440003f40x000000000x00000035
TIM1_IPIDR0x440003f80x000000000x00120002
TIM1_SIDR0x440003fc0x000000000xa3c5dd01
GPIOB_MODER0x500030000xffbfffff0xbfbfffff
GPIOB_AFR[1]0x500030240x0000b0000x1000b000
GPIOC_IDR0x500040100x00001f000x00000f00
RCC_MP_APB2ENSETR0x500007080x000000000x00000001
RCC_MP_APB2ENCLRR0x5000070c0x000000000x00000001

The bare-metal bootloader configures the PWM for 3.0489 kHz operating with 50% duty cycle.

Now let’s configure the PWM in the DTS as follows:

		timers1: timer@44000000 {
			#address-cells = <1>;
			#size-cells = <0>;
			compatible = "st,stm32-timers";
			reg = <0x44000000 0x400>;
			interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 27 IRQ_TYPE_LEVEL_HIGH>,
				     <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
			interrupt-names = "brk", "up", "trg-com", "cc";
			clocks = <&rcc TIM1_K>;
			clock-names = "int";
			status = "okay";

			pwm1: pwm {
				compatible = "st,stm32-pwm";
				#pwm-cells = <3>;
				status = "okay";
				pinctrl-0 = <&pwm1_pins>;
				pinctrl-names = "default";
			};

When the PWM driver module is inserted, we can control the screen brightness as follows:

modprobe pwm-stm32
cd /sys/class/pwm/pwmchip0
echo 2 > export
echo 1000000 > pwm2/period
echo 500000 > pwm2/duty_cycle
echo 1 > pwm2/enable

After that, the registers read:

RegisterValue
TIM1_CR10x00000081
TIM1_SR0x0003001f
TIM1_CCMR20x00000068
TIM1_CCER0x00000500
TIM1_CNT0x00008a35
TIM1_PSC0x00000004
TIM1_ARR0x0000ee33
TIM1_CCR30x0000771a
TIM1_BDTR0x00008000
TIM1_DMAR0x00000081
TIM1_AF10x00000001
TIM1_AF20x00000001
TIM1_VERR0x00000035
TIM1_IPIDR0x00120002
TIM1_SIDR0xa3c5dd01
GPIOB_MODER0xbfbf7fff
GPIOB_AFR[1]0x1000b000
GPIOC_IDR0x00001f00
RCC_MP_APB2ENSETR0x00000001
RCC_MP_APB2ENCLRR0x00000001

Finally, we need to make the panel backlight driver automatically talk to the PWM driver so that the backlight can be adjusted as brightness and not as PWM. We add the following to the DTS:

panel_backlight: panel-backlight {
	compatible = "pwm-backlight";
	pwms = <&pwm1 2 1000000 0>;
	brightness-levels = <0 16 32 64 128 255>;
	default-brightness-level = <4>;
	power-supply = <&v3v3_ao>;
	default-on;
	status = "okay";
};

The panel_backlight is referenced from panel_rgb, but still does not turn on automatically. Apparently the pwm-backlight driver does not have a DTS-controlled “power on” option, so we have to turn it on manually:

echo 0 > /sys/class/backlight/panel-backlight/bl_power

With that, the backlight brightness and power control work as they should via the /sys/class/backlight/ controls.

Display image on LCD

Let’s add two lines to the Buildroot configuration to make the modetest command available:

BR2_PACKAGE_LIBDRM=y
BR2_PACKAGE_LIBDRM_INSTALL_TESTS=y

I am using the Rocktech RK050HR01-CT LCD display and the following DTS fragment:

panel_rgb: panel-rgb {
	compatible = "rocktech,rk050hr18-ctg", "panel-dpi";
	enable-gpios = <&gpiog 7 GPIO_ACTIVE_HIGH>;
	backlight = <&panel_backlight>;
	power-supply = <&v3v3_ao>;
	data-mapping = "rgb565";
	status = "okay";

	width-mm = <108>;
	height-mm = <65>;

	port {
		panel_in_rgb: endpoint {
			remote-endpoint = <&ltdc_out_rgb>;
		};
	};

	panel-timing {
		clock-frequency = <24000000>;
		hactive = <800>;
		vactive = <480>;
		hsync-len = <4>;
		hfront-porch = <8>;
		hback-porch = <8>;
		vsync-len = <4>;
		vfront-porch = <16>;
		vback-porch = <16>;
		hsync-active = <0>;
		vsync-active = <0>;
		de-active = <1>;
		pixelclk-active = <1>;
	};
};

Run just modetest to determine the numbers (CRTC 41, connector 32) of the display:

# modetest | head
trying to open device '/dev/dri/card0'... done
opened device `STMicroelectronics SoC DRM` on driver `stm` (version 1.0.0 at 20170330)
Encoders:
id      crtc    type    possible crtcs  possible clones
31      41      DPI     0x00000001      0x00000001

Connectors:
id      encoder status          name            size (mm)       modes   encoders
32      31      connected       DPI-1           108x65          1       31

Modetest is now able to display a test pattern on the LCD with the following command:

# modetest -M stm -s 32@41:800x480
opened device `STMicroelectronics SoC DRM` on driver `stm` (version 1.0.0 at 20170330)
setting mode 800x480-56.72Hz on connectors 32, crtc 41

Capacitive touchpad (CTP)

Adjust the pin configuration in the DTS according to the way the PCB is wired. For completeness, here’s mine:

i2c5: i2c@4c006000 {
	compatible = "st,stm32mp13-i2c";
	reg = <0x4c006000 0x400>;
	interrupt-names = "event", "error";
	interrupts = <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
		     <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>;
	clocks = <&rcc I2C5_K>;
	resets = <&rcc I2C5_R>;
	#address-cells = <1>;
	#size-cells = <0>;
	st,syscfg-fmp = <&syscfg 0x4 0x10>;
	i2c-analog-filter;
	feature-domains = <&etzpc STM32MP1_ETZPC_I2C5_ID>;
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c5_pins_a>;
	pinctrl-1 = <&i2c5_sleep_pins_a>;
	i2c-scl-rising-time-ns = <170>;
	i2c-scl-falling-time-ns = <5>;
	clock-frequency = <400000>;
	status = "okay";

	goodix: goodix-ts@5d {
		compatible = "goodix,gt911";
		reg = <0x5d>;
		pinctrl-names = "default";
		pinctrl-0 = <&goodix_pins_a>;
		interrupt-parent = <&gpioh>;
		interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
		reset-gpios = <&gpiob 7 GPIO_ACTIVE_LOW>;
		AVDD28-supply = <&v3v3_ao>;
		VDDIO-supply = <&v3v3_ao>;
		touchscreen-size-x = <800>;
		touchscreen-size-y = <480>;
		status = "okay" ;
	};
};

The I2C5 pins are set as follows:

i2c5_pins_a: i2c5-0 {
	pins {
		pinmux = <STM32_PINMUX('H', 13, AF4)>, /* I2C5_SCL */
			 <STM32_PINMUX('F', 3, AF4)>; /* I2C5_SDA */
		bias-disable;
		drive-open-drain;
		slew-rate = <0>;
	};
};

Now verify that the CTP driver has been initialized correctly:

# dmesg | grep -i good
[    1.071181] Goodix-TS 0-005d: ID 911, version: 1060
[    1.074418] input: Goodix Capacitive TouchScreen as /devices/platform/soc/5c007000.etzpc/4c006000.i2c/i2c-0/0-005d/input/input0

Let’s use evtest (add BR2_PACKAGE_EVTEST=y in Buildroot) to verify that we can receive touchpad events. With evtest running, we can touch the touchpad and see that the events are streaming in:

# evtest
Event: time 54.972748, -------------- SYN_REPORT ------------
Event: time 54.976560, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 575
Event: time 54.976560, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 236
Event: time 54.976560, type 3 (EV_ABS), code 0 (ABS_X), value 575
Event: time 54.976560, type 3 (EV_ABS), code 1 (ABS_Y), value 236

All Articles in This Series

Linux

Debugging the Late Boot of STM32MP135

Published 19 Feb 2026. By Jakob Kastelic.

This is Part 9 in the series: Linux on STM32MP135. See other articles.

In this article we will take the custom STM32MP135 board through the end of the Linux boot process. In a previous article we saw the “Booting Linux” message for the first time; now it’s time to run our own programs under the OS and configure some of the devices.

Device Tree Source (DTS)

Having fixed the DDR issues (swizzling gone wrong) in a new revision of the board, wrote the 50 lines of Makefile needed to build the bootloader and kernel and the root file system, it only remains to fix up the device tree source.

The STMicroelectronics kernel repository contains a comprehensive DTS that works on the evaluation board, together with the pin assignment file. It’s an excellent starting point since only a few lines need to be adjusted to accommodate the slightly different pinout used on the larger BGA package (0.8mm) used on my custom board. See the DTS files here for the final result.

The board started to boot but did not find the root filesystem on the SD card, even though the bootloader successfully copied the kernel from the card into DDR. As it turns out, the board miswired the “card detect” pin, so we have to ignore it in the DTS:


&sdmmc1 {
	...
	broken-cd;
	...
}

Init and BusyBox

With the fixed DTS, the boot process finishes with this:

[    1.332380] Run /sbin/init as init process
Hello, world!
$ hello
hello: command not found

Naturally the hello command does not exist, nor any other. Mind you, my init (PID = 1) program is essentially just “Hello, world!” and entirely vibecoded at that.

The next step is to have a more useful set of tools installed on the target, such as BusyBox. We can easily enough build it as follows:

git clone git://busybox.net/busybox.git
cd busybox
make menuconfig # select static build
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

But when we copy the resulting binary as init, we discover that it would really like to have a proper inittab, and a couple device files. With the “Hello, world!” init, we created the root filesystem as follows:

root:
	mkdir -p build/rootfs.dir/sbin
	cp build/init build/rootfs.dir/sbin/init
	dd if=/dev/zero of=build/rootfs bs=1M count=10
	mke2fs -t ext4 -F -d build/rootfs.dir build/rootfs

But to add the device files we need to work as root or do something more clever.

Buildroot

It’s reasonable to yield to the temptation of build automation. (Why the hesitation? Package management and build systems inevitably result in a massive increase in complexity of a system, because it becomes easy to add more stuff into a build.)

Obtain Buildroot:

git clone https://gitlab.com/buildroot.org/buildroot.git

Create a very minimal buildroot/.config such as the following:

BR2_arm=y
BR2_cortex_a7=y
BR2_TOOLCHAIN_BUILDROOT_UCLIBC=y
BR2_PACKAGE_HOST_LINUX_HEADERS_CUSTOM_6_1=y
BR2_CCACHE=y
BR2_CCACHE_DIR="/tmp/buildroot-ccache"
BR2_ENABLE_DEBUG=y
BR2_SHARED_STATIC_LIBS=y
BR2_ROOTFS_DEVICE_CREATION_STATIC=y
BR2_TARGET_GENERIC_ROOT_PASSWD="root"
BR2_ROOTFS_OVERLAY="board/stmicroelectronics/stm32mp135-test/overlay"
BR2_PACKAGE_DROPBEAR=y
BR2_PACKAGE_IPERF3=y
BR2_TARGET_ROOTFS_EXT2=y
BR2_TARGET_ROOTFS_EXT2_4=y
BR2_TARGET_ROOTFS_EXT2_SIZE="5M"

Note the overlay directory: it can be empty, or you can use it to copy any additional files onto the target filesystem. Run make and a long time later (enough to get and build the toolchain and the two selected packages) the root file system image will appear under buildroot/output/images.

The advantage of this approach is that we can quickly and directly rebuild the kernel and the DTB without invoking Buildroot, while being able to quickly get all the packages compiled from Buildroot and included in the target system.

Copying DTB over ssh

So far, installing a new DTB required rebuilding the SD image and writing it to the target using our bootloader. Since DTB is a tiny file, it’s much faster if we could just change the DTB and not the whole SD card image. Let’s do it over ssh so as to easily automate it from the “50 line Makefile”.

First we need to make sure the target has an SSH server. This is provided by the Dropbear package that we have added to the Buildroot configuration. By default, ssh will ask for a password each time which is tedious and insecure. Instead, let’s copy our public key to the overlay directory:

cp ~/.ssh/id_ed25519.pub overlay/root/.ssh/authorized_keys

Moreover, each time Dropbear is started from a “clean” Buildroot-generated rootfs, it will generate its own host key which we’ll have to manually accept. Instead, let’s pre-seed the target key by copying the generated key from the target one time (replace the IP address with that of your target):

mkdir -p overlay/etc/dropbear
scp root@172.25.0.132:/etc/dropbear/dropbear_ed25519_host_key overlay/etc/dropbear

Finally, to install the new DTB over ssh, copy it over and then write it to the second SD card partition:

ssh root@172.25.0.132 "dd of=/dev/mmcblk0p2" < linux/arch/arm/boot/dts/custom.dtb

Restart handler

After installing the new DTB we need to reboot the system. This can be done by power cycling the board, but that quickly get tiresome. In the default configuration, the PSCI interface communicates with the “secure” OS or bootloader (TF-A), but to my mind that’s complexity we can do without.

Thus, we need to implement a “restart handler” so that the reboot command will be able to reboot the system. It will be very simple: it needs to flip one bit in the GRSTCSETR register. As we can read in the RM0475 reference manual for this SoC, that register has only one bit, called MPSYSRST, where writing ‘1’ generates a system reset.

Notice that the device tree already defines a driver to talk to the RCC unit:

rcc: rcc@50000000 {
	compatible = "st,stm32mp13-rcc", "syscon";
	reg = <0x50000000 0x1000>;
	#clock-cells = <1>;
	#reset-cells = <1>;
	clock-names = "hse", "hsi", "csi", "ck_lse", "lsi";
	clocks = <&clk_hse>,
		 <&clk_hsi>,
		 <&clk_csi>,
		 <&clk_lse>,
		 <&clk_lsi>;
};

This st,stm32mp13-rcc driver is to be found under drivers/clk/stm32/clk-stm32mp13.c. First we define the register and bit needed to execute the reset:

#define RCC_MP_GRSTCSETR                0x114
#define RCC_MP_GRSTCSETR_MPSYSRST       BIT(0)

Then the reset handler is very simple:

static int stm32mp1_restart(struct sys_off_data *data)
{
	void __iomem *rcc_base = data->cb_data;

	pr_info("System reset requested...\n");
	dsb(sy);
	writel(RCC_MP_GRSTCSETR_MPSYSRST, rcc_base + RCC_MP_GRSTCSETR);

	while (1)
		wfe();

	return NOTIFY_DONE;
}

Finally, we need to register this reset handler. A good place is at the bottom of stm32mp1_rcc_init():

devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
	SYS_OFF_PRIO_HIGH, stm32mp1_restart, rcc_base);

All Articles in This Series

Incoherent Thoughts

One Move at a Time

Published 14 Feb 2026. By Jakob Kastelic.

Complex problems can be broken down into steps that are small and easy, or, at worst, small and tedious. A big chess game unfolds move by move. In life too, you can only make one move at a time, and then it’s the universe’s turn.

The only question is, what is my move right now? But few ask it. Be aware!

DSP

SHARC+ DSP Over QSPI

Published 9 Feb 2026. By Jakob Kastelic.

In the previous article, we compiled a “blink” program and sent it to the ADSP-2156 chip via UART. Now we can try to do the same over a faster interface: QSPI.

FTDI USB to I2C & SPI

The quad SPI2 interface on the evaluation board EV-SOMCRR-EZLITE is connected to the FT4222HQ-D-T chip that connects to one of the USB-C ports on the carrier boards.

FTDI provides software examples for the FT4222HQ on various operating systems. There is also a Python library, ft4222, which we’ll use for this test.

First we make sure the computer sees the FTDI chip:

import ft4222
for i in range(ft4222.createDeviceInfoList()):
    print(ft4222.getDeviceInfoDetail(i, False))

In the printout, we see FT4222 A and FT4222 B. The first one is used for I2C and SPI, and the second for GPIO.

GPIO expander

The same FTDI part also controls an I2C GPIO expander, ADP5587ACPZ-1, which is in charge of the LEDs on the carrier boards as well as the QSPI interface. We’ll be interested in the following pins at U22:

C0 = USBI_SPI0_EN*
C1 = USBI_SPI1_EN*
C2 = USB_QSPI_EN*
C3 = USB_QSPI_RESET*
C4 = ETH0_RESET
C5 = ADAU1372_PWRDWN*
C6 = PUSHBUTTON_EN
C7 = DS8 (green LED on SOMCRR)
C8 = DS7 (green LED on SOMCRR)
C9 = DS6 (green LED on SOMCRR)

Let’s open the I2C device:

dev = ft4222.openByDescription('FT4222 A')
dev.i2cMaster_Init(100)

We can write to it by means of this helper function:

def wr(d):
    for a, b, c in d:
        dev.i2cMaster_WriteEx(
            a, ft4222.I2CMaster.Flag.START_AND_STOP, bytes([b, c]))

First, we’ll make sure that all pins are set as GPIO (as opposed to “keypad” mode):

wr([(0x30, 0x1D, 0x00)]) # R7:0 -> GPIO
wr([(0x30, 0x1E, 0x00)]) # C7:0 -> GPIO
wr([(0x30, 0x1F, 0x00)]) # C9:8 -> GPIO

Next, we would like to set the pin output states to USBI_SPI0,1 disabled, USB_QSPI neither enabled nor under reset, ETH0 reset asserted, ADAU1372 powered down, pushbutton disabled, LEDs off:

# default output config
wr([(0x30, 0x18, 0b1000_1111)])
wr([(0x30, 0x19, 0b0000_0011)])

Finally, to make this configuration active, we enable the outputs on all port-C pins. Make sure to do this after configuring the pin output states, otherwise you can put the FT4222 into reset (pins default to 0 output states, and pulling USB_QSPI_RESET* low puts the FT4222 into reset).

wr([(0x30, 0x24, 0xff)]) # C7:0 -> output
wr([(0x30, 0x25, 0xff)]) # C9:8 -> output

To test that this all works, we can enable the three green LEDs on the SOMCRR one by one:

wr([(0x30, 0x18, 0b0001_1111)]) # enable LED DS8
wr([(0x30, 0x19, 0b0000_0010)]) # enable LED DS7
wr([(0x30, 0x19, 0b0000_0000)]) # enable LED DS6

These are the green LEDs on the SOMCRR board, not the yellow ones on the SOM board.

QSPI master vs slave

The FT4222 can act either as an SPI master or a slave, and so can the SHARC. Both modes are of interest to us:

FT4222SHARCS5 SetApplication
MasterSlave2-3 / DnBoot SHARC from USB
SlaveMaster1-2 / UpTransmit data from SHARC to USB

S5 is the slide switch located on the SOMCRR board next to the USB-C QSPI connector. With the SOMCRR board oriented so the USB-C connectors are on the top, the S5 slider is in the 1-2 position when it is pushed up (towards the USB-C connectors), and 2-3 when it is down (away from USB-C connectors).

SPI boot

To enable the SPI boot, we need to make three hardware selections:

  1. Enable the SPI interface by pulling USB_QSPI_EN* low:

       wr([(0x30, 0x18, 0b1001_1011)])

    Make sure to follow the steps from the previous section to set the GPIO expander into GPIO mode, set to correct output defaults, etc., or else it will not work!

  1. Flip the S5 switch up so that the 2-3 position is selected, making the FT4222 the SPI master. The switch slider must face away from the USB-C connectors.

  1. Set the rotary boot mode selector switch to mode 2 decimal (010 binary, “External SPI2 host”).

While the elfloader utility supports both -b UARTHOST and -b SPIHOST flags, it appears to generate the same bootstream with either flag. So, let’s try to use the same bootstream as we had used in the Blink example from the previous article, except to make sure to send 0x03 as the first byte as instructed by the SHARC manual:

boot_buffer = bytearray([0x03]) # SPICMD = 0x3: Keep single-bit mode
with open('blink.ldr', 'r') as f:
    for line in f:
        boot_buffer.append(int(line.strip(), 16))

The SHARC hardware reference manual describes SPI mode 1 where the clock idles low and data is sampled on falling edge and shifted out on rising edge:

In SPI slave boot mode, the boot kernel sets the SPI_CTL.CPHA bit and clears the SPI_CTL.CPOL bit. Therefore the SPI_MISO pin is latched on the falling edge of the SPI_MOSI pin.

With that in mind, we can initialize the FT4222 device for SPI as follows, send the bootstream, and close the device:

dev = ft4222.openByDescription('FT4222 A')
dev.spiMaster_Init(
    ft4222.SPIMaster.Mode.SINGLE,
    ft4222.SPIMaster.Clock.DIV_8,
    ft4222.SPI.Cpol.IDLE_LOW,
    ft4222.SPI.Cpha.CLK_TRAILING,
    ft4222.SPIMaster.SlaveSelect.SS0)
dev.spiMaster_SingleWrite(boot_buffer, True)
dev.close()

On my first attempt, this did not work since I had the S5 switch set in the wrong polarity (the slider should face away from the USB-C connectors!). Fixing that, the code boots in an instant—much, much faster than the couple seconds it took over UART.

Boot time

With a bootstream of only about 62 kB, the boot time is not a significant consideration. For larger binaries, ADI offers estimates[1] of how long the boot will take as a function of the binary size. Here’s some representative numbers from the SPI2 figure:

Size / kBSingleDualQuad
20000.3 s0.15 s0.08 s
40000.6 s0.3 s0.15 s
80001.1 s0.6 s0.3 s

Chances are that your bootstream is much, much smaller than these figures, so it would in general add a negligible duration to the boot process.


  1. Analog Devices, Engineer-to-Engineer Note EE-447: Tips and Tricks Using the ADSP-SC59x/ADSP-2159x/ADSP-2156x Processor Boot ROM. V01, May 11, 2023.
Theology

Notes on Cassian, Conference 1

Published 8 Feb 2026. By Jakob Kastelic.

Notes while reading The Conferences of John Cassian, Conf. 1, chs. I–XV.

Latin original is from the collection Corpus scriptorum ecclesiasticorum latinorum, vol. XIII: Iohannis Cassiani Opera, Part II: Conlationes XXIIII. Edited by Michael Petschenig, MDCCCLXXXVI. Available online here.

English translation: The Conferences of John Cassian. Translation and Notes by Edgar C.S. Gibson. From: A Select Library of Nicene and Post-Nicene Fathers of the Christian Church New York, 1894. Available online here.

Places and people

Mentioned in the Preface:

Things better and things worse

Also from the Preface:

Scopon vs telos

In Chapter II of Conference I (hereafter I:II) we find the distinction, supposedly made by “Abbot Moses”, between two kinds of goals. All arts and sciences (omnes artes ac disciplinae) have two kinds of purposes:

Strange practices

At the end of the same chapter (I:II) we find an enumeration of strange practices which are done “for the sake of the kingdom of heaven” (I:III). These are done with some perverse joy, even though Cassian was well aware that most people would find these things unpleasant:

At a first glance, this is pure, undisguised spiritual showing off. Indeed, I:I says that Abbot Moses was reluctant to say anything on the matter lest “he might appear to lay himself open either to the charge of bragging” (iactantia).

Few things, one thing

According to Cassian, the Lord of the gospel of St. Luke has declared that:

“few things” are needful for perfect bliss, i.e., that contemplation which is first secured by reflecting on a few saints: from the contemplation of whom, he who has made some progress rises and attains by God’s help to that which is termed “one thing,” i.e., the consideration of God alone [I:VIII]

It’s not clear to me at this point whether the Lord does really advocate the contemplation of saints at any point in any of the scriptures. St. Paul comes closest, saying “Be ye followers of me, even as I also am of Christ” (1 Cor 11:1).

Saints are special

The Preface warns that we may come across, in these Conferences, things that may appear impossible or very difficult (vel inpossibilia putaverit esse vel dura). They are to be explained by

The really judge it properly, we should try living in the same way:

But if any one wants to give a true opinion on this matter, and is anxious to try whether such perfection can be attained, let him first endeavour to make his purpose their own, with the same zeal and the same mode of life, and then in the end he will find that those things which used to seem beyond the powers of men, are not only possible, but really delightful.

Always on Him?

In I:XIII we get advice that seems to go directly against the contemplation of Saints idea we saw above: “even a momentary departure from gazing on Christ is fornication” (fornicatio: fornication, whoredom, prostitution). Thus the mind, when distracted from its correct focus, should acknowledge the lapse and return to Christ.

Two kinds of joy

Sometimes the phrase “kingdom of God” refers to some eschatological final condition (i.e., heavens). But in I:XIII things are more down to earth, so to speak:

the actual kingdom of God is righteousness and peace and joy, then the man who abides in these is most certainly in the kingdom of God, and on the contrary those who live in unrighteousness, and discord, and the sorrow that worketh death, have their place in the kingdom of the devil, and in hell and death.

But not all kinds of joy would do:

the holy Apostle does not say generally or without qualification that every joy is the kingdom of God, but markedly and emphatically that joy alone which is “in the Holy Ghost.” For he was perfectly aware of another detestable joy, of which we hear “the world shall rejoice,” and “woe unto you that laugh, for ye shall mourn.”

Meditation on God

The idea of constant focus on the highest Good, or meditation on the true Way, is of considerable interest to me as it appears to be a simple, direct answer to the big questions of life: What to do, and why? Unfortunately it’s a case of “easier said than done”. First of all, what does it even mean to say a thing like always gaze on the Christ, all else is whoredom? Cassian admits that there’s no one answer. In I:XV, we get a whole enumeration of the variety of ways to contemplate God (edited for length):

A fine list of meditations! At the end, the flow of language carries Cassian away into an argument not entirely logical:

These considerations certainly no one will preserve lastingly, if anything of carnal affections still survives in him, because “thou canst not,” saith the Lord, “see My face: for no man shall see Me and live;” viz., to this world and to earthly affections.

The reference is to Exodus 33:20, where God is speaking to Moses right after threatening to destroy Israel because of the golden calf incident. Moses asks God, in a bit of a non sequitur, to show his glory, and God refuses for Moses would not be able to live in such a case. But Cassian makes a claim that goes beyond what seems reasonable: it’s perfectly fine to see God, so long as you are dead to the world and earthly affections.

That’s also a point where I lose interest, since speculating on after-death experience is no part of my job description.