diff --git a/.devcontainer b/.devcontainer new file mode 160000 index 0000000..190f80b --- /dev/null +++ b/.devcontainer @@ -0,0 +1 @@ +Subproject commit 190f80babcba5a15b7ea3a0129c2f72cbec2ca0f diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..817c677 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +name: Release + +on: + push: + branches: main + tags: v*.*.* + + pull_request: + types: + - synchronize + - opened + - reopened + +concurrency: + group: ${{ github.workflow }}-${{ github.sha }} + cancel-in-progress: true + + +jobs: + + create_release: + name: Create Release + runs-on: ubuntu-latest + permissions: + contents: write + discussions: write + steps: + - + name: Checkout + uses: actions/checkout@v4 + with: + submodules: true + - + name: Set up QEMU for multi-architecture builds + uses: docker/setup-qemu-action@v2 + - + name: Extract project name + id: extract + run: | + echo proj="$(basename ${{github.workspace}})" >> $GITHUB_OUTPUT + - + name: Build Project + uses: devcontainers/ci@v0.3 + with: + subFolder: "${{github.workspace}}" + configFile: .devcontainer/armhf-container/devcontainer.json + push: never + runCmd: | + "/workspaces/${{steps.extract.outputs.proj}}/build.sh" + - + name: Release + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + fail_on_unmatched_files: true + generate_release_notes: true + files: | + build/*.deb \ No newline at end of file diff --git a/.gitignore b/.gitignore index c6127b3..7c89655 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,5 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf +build/* +deb/boot/* +deb/lib/* +deb/usr/* +tools/build/* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f1b0753 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule ".devcontainer"] + path = .devcontainer + url = https://github.com/EffectiveRange/devcontainer-defs diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..70937e1 --- /dev/null +++ b/Makefile @@ -0,0 +1,55 @@ + +VERSION = $(shell grep Version: deb/DEBIAN/control | cut -d' ' -f2) +# TODO: build module for all kernel versions +KVER ?= 6.1.21+ +TARGET ?= $(error TARGET not specified for deploy ) +WSPDIR = $(shell dirname $(CURDIR) ) +PKGNAME = mrhat-platform +KONAME = er-mrhat-plat + +all: build/$(PKGNAME)_$(VERSION)-1_armhf.deb + @true + +build/$(PKGNAME)_$(VERSION)-1_armhf.deb : driver build/$(PKGNAME).dtbo deb/DEBIAN/* + mkdir -p build + mkdir -p deb/lib/modules/$(KVER) + mkdir -p deb/boot/overlays/ + cp build/$(PKGNAME).dtbo deb/boot/overlays/ + dpkg-deb --root-owner-group --build deb build/$(PKGNAME)_$(VERSION)-1_armhf.deb + +deb/lib/modules/$(KVER)/$(KONAME).ko: driver/*.c driver/Makefile + mkdir -p build + mkdir -p deb/lib/modules/$(KVER)/ + rsync --delete -r ./driver/ /tmp/$(WSPDIR) + schroot -c buildroot -u root -d /tmp/$(WSPDIR) -- make KVER=$(KVER) BQCFLAGS=$(BQCFLAGS) + cp /tmp/$(WSPDIR)/$(KONAME).ko deb/lib/modules/$(KVER)/$(KONAME).ko + +driver: deb/lib/modules/$(KVER)/$(KONAME).ko + @true + +clean: + rm -rf deb/boot/ deb/lib/ build/ + +build/$(PKGNAME).dts.pre: $(PKGNAME).dts + mkdir -p build/ + cpp -nostdinc -undef -x assembler-with-cpp -I/var/chroot/buildroot/usr/src/linux-headers-$(KVER)/include -o build/$(PKGNAME).dts.pre $(PKGNAME).dts + +build/$(PKGNAME).dtbo: build/$(PKGNAME).dts.pre + mkdir -p build/ + dtc -I dts -O dtb -o build/$(PKGNAME).dtbo build/$(PKGNAME).dts.pre + +deploy: all + rsync -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" -avhz --progress build/$(PKGNAME)_$(VERSION)-1_armhf.deb $(TARGET):/tmp/ + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- sudo dpkg -r $(PKGNAME) + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- sudo dpkg -i /tmp/$(PKGNAME)_$(VERSION)-1_armhf.deb + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- sudo sed -ri '/^\s*dtoverlay=$(PKGNAME)/d' /boot/config.txt + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- "echo dtoverlay=$(PKGNAME) | sudo tee -a /boot/config.txt" + +quickdeploy: driver + scp deb/lib/modules/$(KVER)/$(KONAME).ko $(TARGET):/tmp/ + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- sudo cp /tmp/$(KONAME).ko /lib/modules/$(KVER)/ + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- "sudo rmmod $(KONAME) || true" + ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null $(TARGET) -- "sudo modprobe $(KONAME) || true" + + +.PHONY: clean all deploy quickdeploy driver diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..86bc054 --- /dev/null +++ b/build.sh @@ -0,0 +1,9 @@ +#!/bin/bash +pushd $(dirname $0) +LAST_KVER="" +for kver in $(ls -1 /var/chroot/buildroot/lib/modules); +do +LAST_KVER=$kver +make driver KVER=$kver +done +make all KVER=$LAST_KVER \ No newline at end of file diff --git a/deb/DEBIAN/control b/deb/DEBIAN/control new file mode 100644 index 0000000..3860f5e --- /dev/null +++ b/deb/DEBIAN/control @@ -0,0 +1,5 @@ +Package: mrhat-platform +Version: 0.1.0 +Maintainer: Effective Range Kft. +Architecture: armhf +Description: Device driver and device tree overlay for the Effective Range MrHat extension hat for RaspberryPi diff --git a/deb/DEBIAN/postinst b/deb/DEBIAN/postinst new file mode 100755 index 0000000..a9c6025 --- /dev/null +++ b/deb/DEBIAN/postinst @@ -0,0 +1,11 @@ +#!/bin/bash +if [[ -z "$(grep er-mrhat-plat /etc/modules)" ]]; +then + echo "Adding er-mrhat-plat to /etc/modules ..." + echo 'er-mrhat-plat' >> /etc/modules + depmod +fi + +echo "Please add 'dtoverlay=mrhat-platform' if needed, then reboot ..." + + diff --git a/deb/DEBIAN/postrm b/deb/DEBIAN/postrm new file mode 100755 index 0000000..8a5c5f1 --- /dev/null +++ b/deb/DEBIAN/postrm @@ -0,0 +1,7 @@ +#!/bin/bash +echo "Removing module autoload for er-mrhat-plat ..." +sed -ri '/^\s*er-mrhat-plat/d' /etc/modules + +depmod + +echo "Removal of mrhat-platform completed, please remove 'dtoverlay=mrhat-platform' from /boot/config.txt if not needed anymore then reboot ..." diff --git a/deb/DEBIAN/preinst b/deb/DEBIAN/preinst new file mode 100755 index 0000000..24a9d02 --- /dev/null +++ b/deb/DEBIAN/preinst @@ -0,0 +1,3 @@ +#!/bin/bash +# removing existing dtb file, as it blocks installation +rm -f /boot/overlays/mrhat-platform.dtbo \ No newline at end of file diff --git a/driver/Makefile b/driver/Makefile new file mode 100644 index 0000000..95d0974 --- /dev/null +++ b/driver/Makefile @@ -0,0 +1,12 @@ +obj-m += er-mrhat-plat.o + +KVER ?= $(shell uname -r) +PWD := $(CURDIR) + +CFLAGS_er-mrhat-plat.o := -O2 -Wall -Werror $(BQCFLAGS) + +all: + make -C /lib/modules/$(KVER)/build M=$(PWD) modules + +clean: + make -C /lib/modules/$(KVER)/build M=$(PWD) clean \ No newline at end of file diff --git a/driver/er-mrhat-plat.c b/driver/er-mrhat-plat.c new file mode 100644 index 0000000..109b1a7 --- /dev/null +++ b/driver/er-mrhat-plat.c @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: GPL-2.0 +// MrHat platform driver +// Copyright (C) 2024 Ferenc Janky & Attila Gombos + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct mrhat_device { + struct device *dev; + struct timer_list hb_timer; + struct workqueue_struct *wq; + struct work_struct hb_work; + int hb_level; + struct gpio_desc *gpio_hb; + u32 period_ms; + u32 pulse_width_ms; + u32 pulse_to_period_ms; +}; + +static void mrhat_hb_work_func(struct work_struct *work) { + unsigned long jiffies_start = jiffies; + unsigned long jiffies_end; + struct mrhat_device *mrhat_dev = + container_of(work, struct mrhat_device, hb_work); + int curr_level = mrhat_dev->hb_level; + mrhat_dev->hb_level = mrhat_dev->hb_level ? 0 : 1; + gpiod_set_value_cansleep(mrhat_dev->gpio_hb, mrhat_dev->hb_level); + jiffies_end = jiffies; + mod_timer(&mrhat_dev->hb_timer, + jiffies + + (curr_level ? msecs_to_jiffies(mrhat_dev->pulse_to_period_ms) + : msecs_to_jiffies(mrhat_dev->pulse_width_ms)) - + (jiffies_end - jiffies_start)); +} + +static void timer_callback(struct timer_list *t) { + struct mrhat_device *mrhat_dev = + container_of(t, struct mrhat_device, hb_timer); + queue_work(mrhat_dev->wq, &mrhat_dev->hb_work); +} + +static const struct of_device_id mrhat_dt_ids[] = {{ + .compatible = "er,mrhat", + }, + {}}; +MODULE_DEVICE_TABLE(of, mrhat_dt_ids); + +static void mrhat_cleanup_workqueue(void *data) { + struct mrhat_device *mrhat_dev = data; + dev_info(mrhat_dev->dev, "cleaning up workqueue"); + destroy_workqueue(mrhat_dev->wq); +} + +static void mrhat_cleanup_work(void *data) { + struct work_struct *work = data; + struct mrhat_device *mrhat_dev = + container_of(work, struct mrhat_device, hb_work); + dev_info(mrhat_dev->dev, "cleaning up work struct"); + cancel_work_sync(work); +} + +static void mrhat_cleanup_timer(void *data) { + struct timer_list *timer = data; + struct mrhat_device *mrhat_dev = + container_of(timer, struct mrhat_device, hb_timer); + dev_info(mrhat_dev->dev, "cleaning up timer"); + del_timer_sync(timer); +} + +static int er_mrhat_probe(struct platform_device *pdev) { + int ret; + + struct mrhat_device *mrhat_dev = + devm_kzalloc(&pdev->dev, sizeof(struct mrhat_device), GFP_KERNEL); + if (IS_ERR(mrhat_dev)) { + dev_err(&pdev->dev, "failed to allocate memory for device struct"); + return PTR_ERR(mrhat_dev); + } + mrhat_dev->dev = &pdev->dev; + platform_set_drvdata(pdev, mrhat_dev); + + mrhat_dev->gpio_hb = + devm_gpiod_get(mrhat_dev->dev, "heartbeat", GPIOD_OUT_HIGH); + if (IS_ERR(mrhat_dev->gpio_hb)) { + ret = PTR_ERR(mrhat_dev->gpio_hb); + dev_err(mrhat_dev->dev, "failed to request GPIO pin:%d", ret); + return ret; + } + mrhat_dev->hb_level = 1; + + mrhat_dev->wq = create_singlethread_workqueue("hb_wq"); + if (IS_ERR(mrhat_dev->wq)) { + ret = PTR_ERR(mrhat_dev->wq); + dev_err(mrhat_dev->dev, "failed to create workqueue:%d", ret); + return ret; + } + + ret = devm_add_action_or_reset(mrhat_dev->dev, mrhat_cleanup_workqueue, + mrhat_dev); + if (ret) { + dev_err(mrhat_dev->dev, "failed to register cleanup action for workqueue"); + return ret; + } + + INIT_WORK(&mrhat_dev->hb_work, mrhat_hb_work_func); + + ret = devm_add_action_or_reset(mrhat_dev->dev, mrhat_cleanup_work, + &mrhat_dev->hb_work); + if (ret) { + dev_err(mrhat_dev->dev, + "failed to register cleanup action for work thread"); + return ret; + } + + timer_setup(&mrhat_dev->hb_timer, timer_callback, 0); + + ret = devm_add_action_or_reset(mrhat_dev->dev, mrhat_cleanup_timer, + &mrhat_dev->hb_timer); + if (ret) { + dev_err(mrhat_dev->dev, "failed to register cleanup action for timer"); + return ret; + } + + ret = device_property_read_u32(mrhat_dev->dev, "er,heartbeat-period-ms", + &mrhat_dev->period_ms); + if (ret) { + dev_err(mrhat_dev->dev, "failed to read heartbeat period dt property:%d", + ret); + return ret; + } + + ret = device_property_read_u32(mrhat_dev->dev, "er,heartbeat-pulse-width-ms", + &mrhat_dev->pulse_width_ms); + if (ret) { + dev_err(mrhat_dev->dev, + "failed to read heartbeat pulse width dt property:%d", ret); + return ret; + } + + mrhat_dev->pulse_to_period_ms = + mrhat_dev->period_ms - mrhat_dev->pulse_width_ms; + + if (mrhat_dev->period_ms < 2 || mrhat_dev->pulse_to_period_ms < 1 || + mrhat_dev->pulse_to_period_ms < mrhat_dev->period_ms / 2 || + mrhat_dev->pulse_to_period_ms > mrhat_dev->period_ms) { + dev_err(mrhat_dev->dev, + "invalid heartbeat pulse settings received period:%u pulse:%u " + "period2pulse:%u", + mrhat_dev->period_ms, mrhat_dev->pulse_width_ms, + mrhat_dev->pulse_to_period_ms); + return -EINVAL; + } + + mod_timer(&mrhat_dev->hb_timer, + jiffies + msecs_to_jiffies(mrhat_dev->pulse_width_ms)); + + dev_info(mrhat_dev->dev, "mrhat driver successfully initialized"); + return 0; +} + +static struct platform_driver mrhat_driver = { + .driver = + { + .name = "er-mrhat-plat", + .of_match_table = mrhat_dt_ids, + }, + .probe = er_mrhat_probe, +}; + +module_platform_driver(mrhat_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ferenc Janky"); +MODULE_AUTHOR("Attila Gombos"); +MODULE_DESCRIPTION( + "Platform driver for the MrHATv1 family of RaspberryPi extension hats"); +MODULE_VERSION("0.1.0"); diff --git a/drv-mrhat.code-workspace b/drv-mrhat.code-workspace new file mode 100644 index 0000000..1327b39 --- /dev/null +++ b/drv-mrhat.code-workspace @@ -0,0 +1,20 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + "clangd.arguments": [ + "-header-insertion=never" + ], + "clangd.fallbackFlags": [ + "-isystem/var/chroot/buildroot/usr/src/linux-headers-6.1.21-v7+/include", + "-isystem/var/chroot/buildroot/usr/src/linux-headers-6.1.21-v7+/arch/arm/include", + ], + "files.associations": { + "reboot.h": "c", + "gpio.h": "c" + } + } +} \ No newline at end of file diff --git a/mrhat-platform.dts b/mrhat-platform.dts new file mode 100644 index 0000000..d7c7282 --- /dev/null +++ b/mrhat-platform.dts @@ -0,0 +1,51 @@ +/* +* Copyright (C) 2024 Effective Range Kft. +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ +/dts-v1/; +/plugin/; + +#include +#include + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target-path = "/"; + + __overlay__ { + status = "okay"; + mrhat: mrhat { + compatible = "er,mrhat"; + heartbeat-gpios = <&gpio 0x19 GPIO_ACTIVE_HIGH>; + er,heartbeat-period-ms = <1000>; + er,heartbeat-pulse-width-ms = <20>; + status = "okay"; + }; + }; + }; + + fragment@1 { + target = <&gpio>; + __overlay__ { + mrhat_pins: mrhat_pins { + brcm,pins = <0x19>; + brcm,function = <0x1>; /* out */ + brcm,pull = <0>; /* none */ + }; + }; + }; + + __overrides__ { + /* GPIO number of heartbeat pin */ + hb_pin = <&mrhat>, "heartbeat-gpios:4", + <&mrhat_pins>, "brcm,pins:0"; + hb_period_ms = <&mrhat>, "er,heartbeat-period-ms:0"; + hb_pulse_ms = <&mrhat>, "er,heartbeat-pulse-width-ms:0"; + }; + +};