From a14a7aa543becde1e1d4e5159d0f7d6e2413ba5d Mon Sep 17 00:00:00 2001 From: Luxferre Date: Mon, 19 Oct 2026 23:34:06 +0300 Subject: [PATCH] Add GC2395/CAMSS rear camera driver and fix audio, earpiece, micbias, and modem routing --- MANUAL.md | 79 +- README.md | 15 +- overlay/rootfs/etc/doas.d/50-sxmo.conf | 6 - overlay/rootfs/etc/modules | 1 + overlay/rootfs/usr/bin/sxmo_audio.sh | 317 ++++++ overlay/rootfs/usr/bin/sxmo_modemaudio.sh | 63 + overlay/rootfs/usr/bin/sxmo_ob.sh | 29 + .../usr/share/alsa/ucm2/msm8916/HiFi.conf | 8 +- .../alsa/ucm2/platforms/msm8916/qdsp6.conf | 15 + .../rootfs/usr/share/q6voiced/q6voiced.conf | 2 + .../camera/msm8905-cam.ko | Bin 0 -> 17088 bytes .../camera/msm8905-cam.ko.zst | Bin 0 -> 7732 bytes .../leds/leds-msm8905-torch.ko | Bin 7596 -> 7912 bytes .../leds/leds-msm8905-torch.ko.zst | Bin 0 -> 3143 bytes scripts/apply-bananarchy-overlay.sh | 10 +- scripts/build-kernel-audio.sh | 38 +- scripts/configure-rootfs-from-scratch.sh | 25 +- src/leds-msm8905-torch/leds-msm8905-torch.c | 41 +- src/msm8905-cam/Makefile | 10 + src/msm8905-cam/msm8905-cam.c | 1013 +++++++++++++++++ 20 files changed, 1623 insertions(+), 49 deletions(-) create mode 100755 overlay/rootfs/usr/bin/sxmo_audio.sh create mode 100755 overlay/rootfs/usr/bin/sxmo_modemaudio.sh create mode 100755 overlay/rootfs/usr/bin/sxmo_ob.sh create mode 100644 overlay/rootfs/usr/share/alsa/ucm2/platforms/msm8916/qdsp6.conf create mode 100644 overlay/rootfs/usr/share/q6voiced/q6voiced.conf create mode 100644 prebuilt/kernel-6.12.1-msm8916/camera/msm8905-cam.ko create mode 100644 prebuilt/kernel-6.12.1-msm8916/camera/msm8905-cam.ko.zst create mode 100644 prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst create mode 100644 src/msm8905-cam/Makefile create mode 100644 src/msm8905-cam/msm8905-cam.c diff --git a/MANUAL.md b/MANUAL.md index f861563..254022e 100644 --- a/MANUAL.md +++ b/MANUAL.md @@ -31,16 +31,21 @@ This manual covers flashing, operating, and customizing **Bananarchy Linux** (`v - Includes instant case toggling during preedit, full 32-character ASCII punctuation/symbol entry, terminal control sequences (`^A`–`^Z`, `Esc`, `Tab`, `F1`–`F12`), and a `450ms` long-press `*` `Ctrl+` modifier latch. 3. **Bantam AI agent (`prebuilt/bantam/bantam`)**: - Fetched from the latest `master` branch of `https://code.luxferre.top/luxferre/bantam-cpp`, cross-compiled for `armv7`, and installed to `/usr/bin/bantam` and `/usr/local/bin/bantam` (with no default `.bantam.cfg` pre-created). -4. **Mainline 6.12.1-msm8916 QDSP6 audio, voice call & LTE modem stack**: +4. **Mainline 6.12.1-msm8916 QDSP6 audio (loudspeaker, earpiece, MEMS & headset mics), voice call & LTE modem stack**: - Patched `q6core`, `q6afe`, `q6afe-dai`, `q6voice-dai`, `q6mvm`, and device trees (`VoiceMMode1` DAI + PM8909 WCD codec). - - WirePlumber `50-s16le.lua` (`S16LE` sample format enforcement), ALSA UCM2 profiles (`HiFi.conf` and `VoiceCall.conf`), `q6voiced`, `rmtfs`, `msm-modem-uim-selection`, `msm8909-modem-wwan-port`, and `ModemManager`. -5. **Memory & swap tuning for 512 MB RAM**: + - Automatic PM8909 WCD analog codec register initialization at boot (`CDC_A_MICB_1_VAL` `0xf141 = 0x60` for 2.8V internal MEMS microphone bias, and `CDC_A_RX_EAR_CTL` `0xf1c0 |= 0x20` for +6 dB earpiece PA gain). + - WirePlumber `50-s16le.lua` (`S16LE` sample format enforcement), ALSA UCM2 profiles (`qdsp6.conf`, `HiFi.conf`, and `VoiceCall.conf`), `q6voiced` (`card=0, device=2`), `sxmo_audio.sh` / `sxmo_modemaudio.sh` PipeWire UCM profile switching, `sxmo_ob.sh` (`WOB_DISABLE_PLEDGE=1`), `rmtfs`, `msm-modem-uim-selection`, `msm8909-modem-wwan-port`, and `ModemManager` (`mmcli`). +5. **Rear 2MP camera (`src/msm8905-cam/`) & SY7807 torch LED (`src/leds-msm8905-torch/`)**: + - `msm8905-cam.ko`: powers PM8909 LDOs (`L2` 1.2V, `L6` 1.8V, `L17` 2.85V) and `CAMSS_MCLK0` (24 MHz on GPIO 26), configures the GalaxyCore `GC2395` MIPI CSI-2 sensor (`blsp_i2c3` `0x37`) and MSM8909 `CAMSS` (`CSIPHY0 -> CSID0 -> ISPIF -> VFE0 RDI0 -> VBIF -> CMA`), and exposes `/dev/video0` (`800x600` 24-bit `RGB3` V4L2 capture device). + - `leds-msm8905-torch.ko`: exposes `/sys/class/leds/white:torch` (`GPIO 31`), configures WCD micbias/earpiece registers at boot, and hooks PM8909 `PON` clean shutdown. +6. **Memory & swap tuning for 512 MB RAM**: - `384 MiB` ZSTD-compressed `zram0` swap (priority `100`) + `512 MiB` `/swapfile` (priority `10`), with `vm.swappiness=100` and `vm.page-cluster=0`. -6. **Preinstalled on-device development & scripting stack**: +7. **Preinstalled on-device development & scripting stack**: - **C / C++ toolchain & build tools**: `gcc`, `g++`, `musl-dev`, `make`, `git`, `nano` - **Janet**: `janet`, `janet-dev`, `jpm` (Janet package manager) - **Perl**: `perl`, `perl-io-socket-ssl` (`IO::Socket::SSL`) - **Tcl & SQLite**: `tcl`, `tcl-lib` (`tcllib`), `tcl-tls`, `sqlite` (`sqlite3`), `sqlite-tcl` (`tcl-sqlite`) + - **Hardware & media utilities**: `v4l-utils` (`v4l2-ctl`), `i2c-tools`, `ffmpeg`, `mpv`, `imv` --- @@ -218,10 +223,74 @@ You can switch between **vertical (`splitv`, one under another)**, **horizontal ## 4. Default credentials, USB SSH & Bantam AI agent - **Username**: `user` -- **Password**: `147147` (`doas` and `sudo` enabled) +- **Password**: `147147` (`doas` and `sudo` enabled, with passwordless `doas` configured for `wheel` in `/etc/doas.d/50-sxmo.conf`) - **USB networking SSH**: ```bash ssh user@172.16.42.1 ``` - **Bantam AI agent**: Run `bantam` in the terminal (`Foot`) or over SSH. Configure your endpoint/API key via environment variables or `~/.bantam.cfg` when needed. + +--- + +## 5. Camera, audio, flashlight & cellular modem reference + +### 5.1 Taking photos with the rear camera (`/dev/video0`) + +The `msm8905-cam` kernel module drives the rear 2MP GalaxyCore `GC2395` sensor through MSM8909 `CAMSS` (`CSIPHY0 -> CSID0 -> ISPIF -> VFE0 -> VBIF`) and registers `/dev/video0` (`800x600` 24-bit `RGB3`). + +To capture a photo to `~/photo.jpg` (or `.png`), stream a frame with `v4l2-ctl` and encode it with `ffmpeg -f rawvideo`: +```bash +v4l2-ctl -d /dev/video0 \ + --set-fmt-video=width=800,height=600,pixelformat=RGB3 \ + --stream-mmap --stream-count=1 --stream-to=/tmp/frame.rgb && \ +ffmpeg -hide_banner -loglevel error \ + -f rawvideo -pixel_format rgb24 -video_size 800x600 \ + -i /tmp/frame.rgb -frames:v 1 -y ~/photo.jpg && \ +rm -f /tmp/frame.rgb +``` +- **Viewing photos on the phone**: run `imv ~/photo.jpg` or `mpv ~/photo.jpg`. +- **Why `v4l2-ctl` + `ffmpeg -f rawvideo` is used**: Alpine's `libavdevice` links against `libv4l2.so.0`, whose `v4l2_mmap` wrapper on 32-bit ARM (`musl` 64-bit `off_t`) misaligns variadic `syscall(SYS_mmap2, ...)` arguments. Capturing via `v4l2-ctl` and encoding via `ffmpeg -f rawvideo` bypasses `libv4l2` completely. + +### 5.2 Switching speakers (loudspeaker vs. earpiece) & recording microphone audio + +Both the rear loudspeaker (`Speaker` / `SPK_OUT`) and the front earpiece speaker (`Earpiece` / `EAR`, boosted by +6 dB in `CDC_A_RX_EAR_CTL`), as well as the internal MEMS microphone (`Mic1` / `AMIC3`, biased at 2.8V in `CDC_A_MICB_1_VAL`) and 3.5mm headset microphone (`Mic2` / `AMIC2`), are managed through PipeWire/WirePlumber and `sxmo_audio.sh`: + +- **List available audio outputs (`Speaker`, `Earpiece`, `Headphones`)**: + ```bash + sxmo_audio.sh out list + ``` +- **Switch active output to the rear loudspeaker**: + ```bash + sxmo_audio.sh out set "Speaker" + ``` +- **Switch active output to the front earpiece speaker**: + ```bash + sxmo_audio.sh out set "Earpiece" + ``` +- **Record from the internal MEMS microphone (`Mic1`)**: + ```bash + pw-record --rate=48000 --channels=1 --format=s16 ~/recording.wav + ``` + +### 5.3 Flashlight (`white:torch`) & keypad backlight (`white:kbd_backlight`) + +- **Turn rear flashlight on / off**: + ```bash + brightnessctl -d white:torch set 1 + brightnessctl -d white:torch set 0 + ``` +- **Set keypad backlight brightness (`0`–`255`)**: + ```bash + brightnessctl -d white:kbd_backlight set 255 + ``` + +### 5.4 GSM / WCDMA / LTE modem & voice calls (`ModemManager` & `q6voiced`) + +- **Inspect modem registration, operator, and signal quality**: + ```bash + mmcli -m any + ``` +- **Place or manage voice calls and SMS**: + Use the Sxmo **Phone** / **Texts** menu (`Left softkey`) or `mmcli -m any --voice-list-calls` / `mmcli -m any --messaging-list-sms`. In-call audio is automatically routed between the modem (`VoiceMMode1`) and the PM8909 WCD codec by the `q6voiced` service and `sxmo_modemaudio.sh`. + diff --git a/README.md b/README.md index ec0fe70..d00183c 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ See [The Bananarchy Manifesto](./MANIFESTO.md) for the rationale behind this entire project. -Bananarchy targets the **Sxmo (`sxmo-de-sway`)** environment exclusively, tuned for 1:1 native `240x320` resolution, enlarged high-contrast typography, sub-115 MB idle RAM footprint on 512 MB devices, full Qualcomm QDSP6/Q6Voice audio and LTE modem routing, pre-integrated **Bantam** AI agent (`https://code.luxferre.top/luxferre/bantam-cpp`), a complete on-device development & scripting stack (`git`, `gcc`/`g++`, `make`, `nano`, `janet` + `jpm`, `perl` + `IO::Socket::SSL`, `tcl` + `tcllib`/`tcl-tls`/`sqlite-tcl`, `sqlite`), and a custom **6-mode `fcitx5-t9` input engine** supporting lowercase, one-shot Shift, Caps Lock, all 32 ASCII punctuation symbols, terminal control codes (`^A`-`^Z`, `Esc`, `Tab`, `F1`-`F12`), and direct numeric entry. +Bananarchy targets the **Sxmo (`sxmo-de-sway`)** environment exclusively, tuned for 1:1 native `240x320` resolution, enlarged high-contrast typography, sub-115 MB idle RAM footprint on 512 MB devices, full Qualcomm QDSP6/Q6Voice audio (loudspeaker, earpiece speaker, and internal/headset microphones with PM8909 WCD analog codec 2.8V micbias and +6 dB EAR PA gain), GSM/WCDMA/LTE modem routing (`ModemManager` + `q6voiced`), rear 2MP camera support (`msm8905-cam` V4L2 driver for GalaxyCore `GC2395` + MSM8909 `CAMSS` on `/dev/video0`), pre-integrated **Bantam** AI agent (`https://code.luxferre.top/luxferre/bantam-cpp`), a complete on-device development & scripting stack (`git`, `gcc`/`g++`, `make`, `nano`, `janet` + `jpm`, `perl` + `IO::Socket::SSL`, `tcl` + `tcllib`/`tcl-tls`/`sqlite-tcl`, `sqlite`, `i2c-tools`, `v4l-utils`), and a custom **6-mode `fcitx5-t9` input engine** supporting lowercase, one-shot Shift, Caps Lock, all 32 ASCII punctuation symbols, terminal control codes (`^A`-`^Z`, `Esc`, `Tab`, `F1`-`F12`), and direct numeric entry. The current Bananarchy version is `v0.0.1`. @@ -30,7 +30,7 @@ The current Bananarchy version is `v0.0.1`. bananarchy/ ├── README.md # Project architecture, build system, and quickstart guide ├── MANIFESTO.md # The Bananarchy Manifesto -├── MANUAL.md # Complete flashing, keypad, T9, Bantam, and modem manual +├── MANUAL.md # Complete flashing, keypad, T9, camera, audio, Bantam, and modem manual ├── flash-edl.sh # Automated USB EDL flasher wrapper (sudo + ModemManager handling) ├── flash-edl.py # Multi-device Qualcomm Sahara/Firehose EDL flasher ├── rebuild-image.sh # Builds boot.img, MBR raw .img, and Android .sparse.img @@ -69,17 +69,18 @@ bananarchy/ │ └── 0001-bananarchy-multimode-t9-and-fcitx5-5.1.19.patch ├── src/ │ ├── fcitx5-t9/ # C++20 source of Bananarchy's 6-mode fcitx5-t9 input method -│ └── leds-msm8905-torch/ # Kernel driver for SY7807 rear torch LED & PM8909 PON shutdown +│ ├── leds-msm8905-torch/ # Kernel driver for SY7807 rear torch LED, WCD codec init & PM8909 PON shutdown +│ └── msm8905-cam/ # Kernel V4L2 driver for GC2395 2MP rear camera & MSM8909 CAMSS (/dev/video0) ├── overlay/ -│ └── rootfs/ # Complete Bananarchy rootfs overlay (OS branding, Sxmo, audio, T9) +│ └── rootfs/ # Complete Bananarchy rootfs overlay (OS branding, Sxmo, audio, camera, T9) ├── prebuilt/ -│ ├── kernel-6.12.1-msm8916/ # Prebuilt QDSP6/Q6Voice .ko modules, torch .ko, and 5 device DTBs +│ ├── kernel-6.12.1-msm8916/ # Prebuilt QDSP6/Q6Voice .ko, torch .ko, camera .ko, and 5 device DTBs │ ├── fcitx5-t9/ # Prebuilt armv7 libt9.so and fcitx5 addon/inputmethod configs │ └── bantam/ # Prebuilt armv7 bantam AI agent binary └── scripts/ ├── generate-artwork.py # Generates all wallpapers, icons, and splashes from icon-large.png ├── mipi-dbi-cmd.py # Compiles MIPI-DBI panel .txt init sequences into .bin firmware - ├── build-kernel-audio.sh # Cross-compiles Linux 6.12.1-msm8916 QDSP6/torch modules & DTBs + ├── build-kernel-audio.sh # Cross-compiles Linux 6.12.1-msm8916 QDSP6/torch/camera modules & DTBs ├── build-fcitx5-t9.sh # Cross-compiles src/fcitx5-t9 for armv7 ├── build-bantam.sh # Fetches master from https://code.luxferre.top/luxferre/bantam-cpp & cross-compiles for armv7 ├── apply-bananarchy-overlay.sh # Applies overlay, prebuilts, artwork, and device DTB to rootfs @@ -105,7 +106,7 @@ All components are pre-compiled in `prebuilt/` and `overlay/rootfs/`, and can be # Fetch latest master from https://code.luxferre.top/luxferre/bantam-cpp and cross-compile Bantam AI agent ./scripts/build-bantam.sh -# Rebuild Linux 6.12.1-msm8916 QDSP6/Q6Voice audio modules & DTBs +# Rebuild Linux 6.12.1-msm8916 QDSP6/Q6Voice audio modules, torch LED module, camera module & DTBs ./scripts/build-kernel-audio.sh ``` diff --git a/overlay/rootfs/etc/doas.d/50-sxmo.conf b/overlay/rootfs/etc/doas.d/50-sxmo.conf index d079a5e..c4f8e1f 100644 --- a/overlay/rootfs/etc/doas.d/50-sxmo.conf +++ b/overlay/rootfs/etc/doas.d/50-sxmo.conf @@ -2,13 +2,7 @@ permit persist :wheel permit nopass :wheel as root cmd busybox args poweroff permit nopass :wheel as root cmd busybox args reboot permit nopass :wheel as root cmd poweroff -permit nopass :wheel as root cmd /sbin/poweroff permit nopass :wheel as root cmd reboot -permit nopass :wheel as root cmd /sbin/reboot -permit nopass :wheel as root cmd halt -permit nopass :wheel as root cmd /sbin/halt -permit nopass :wheel as root cmd openrc-shutdown -permit nopass :wheel as root cmd /sbin/openrc-shutdown permit nopass :wheel as root cmd sxmo_wifitoggle.sh permit nopass setenv { SXMO_OS } :wheel as root cmd sxmo_bluetoothtoggle.sh permit nopass :wheel as root cmd rc-service args bluetooth restart diff --git a/overlay/rootfs/etc/modules b/overlay/rootfs/etc/modules index 4a73ac1..7d5021e 100644 --- a/overlay/rootfs/etc/modules +++ b/overlay/rootfs/etc/modules @@ -2,3 +2,4 @@ af_packet ipv6 panel_mipi_dbi leds_msm8905_torch +msm8905-cam diff --git a/overlay/rootfs/usr/bin/sxmo_audio.sh b/overlay/rootfs/usr/bin/sxmo_audio.sh new file mode 100755 index 0000000..fe5d38a --- /dev/null +++ b/overlay/rootfs/usr/bin/sxmo_audio.sh @@ -0,0 +1,317 @@ +#!/bin/sh +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2022 Sxmo Contributors + +# shellcheck source=configs/default_hooks/sxmo_hook_icons.sh +. sxmo_hook_icons.sh +. sxmo_common.sh + +set -e + +notifyvol() { + vol="$1" + if [ "-" = "$vol" ]; then + vol="$(cat)" + fi + if [ "muted" = "$vol" ]; then + vol=0 + fi + if [ -z "$SXMO_WOB_DISABLE" ]; then + printf "%s\n" "$vol" > "$XDG_RUNTIME_DIR"/sxmo.obsock + else + notify-send -r 999 "$icon_audio Volume $vol" + fi +} + +# adjust *output* vol/mute +volup() { + pactl set-sink-volume @DEFAULT_SINK@ +"${1:-5}%" +} + +voldown() { + pactl set-sink-volume @DEFAULT_SINK@ -"${1:-5}%" +} + +voltogglemute() { + pactl set-sink-mute @DEFAULT_SINK@ toggle +} + +volismuted() { + pactl get-sink-mute @DEFAULT_SINK@ | grep -q "Mute: yes" +} + +volget() { + if volismuted; then + printf "muted" + else + pactl get-sink-volume @DEFAULT_SINK@ | head -n1 | cut -d'/' -f2 | sed 's/ //g' | sed 's/\%//' + fi +} + +volset() { + pactl set-sink-volume @DEFAULT_SINK@ "$1"% +} + +# adjust *input* vol/mute +micvolup() { + pactl set-source-volume @DEFAULT_SOURCE@ +"${1:-5}%" +} + +micvoldown() { + pactl set-source-volume @DEFAULT_SOURCE@ -"${1:-5}%" +} + +mictogglemute() { + pactl set-source-mute @DEFAULT_SOURCE@ toggle +} + +micismuted() { + pactl get-source-mute @DEFAULT_SOURCE@ | grep -q "Mute: yes" +} + +micvolget() { + if micismuted; then + printf "muted" + else + pactl get-source-volume @DEFAULT_SOURCE@ | head -n1 | cut -d'/' -f2 | sed 's/ //g' | sed 's/\%//' + fi +} + +micvolset() { + pactl set-source-volume @DEFAULT_SOURCE@ "$1"% +} + +# set the *active port* for output +deviceset() { + if pactl --format=json list sinks | jq -e ".[] | select(.name == \"$(pactl get-default-sink)\") | .ports[] | select(.name == \"[Out] $1\")" >/dev/null 2>&1; then + pactl set-sink-port @DEFAULT_SINK@ "[Out] $1" + else + prof="$(pactl --format=json list cards | jq -r ".[] | . as \$c | .ports[\"[Out] $1\"]? | select(. != null and .availability != \"not available\") | .profiles[] | select(\$c.profiles[.].available == true) | \"\(\$c.name) \(.)\"" | head -n1)" + if [ -n "$prof" ]; then + card="$(printf "%s" "$prof" | cut -f1)" + profile="$(printf "%s" "$prof" | cut -f2)" + pactl set-card-profile "$card" "$profile" + fi + fi +} + +# set the *active port* for input +devicesetinput() { + pactl set-source-port @DEFAULT_SOURCE@ "[In] $1" +} + +# get the *active port* for input +devicegetinput() { + [ -z "$1" ] && default_source="$(pactl get-default-source)" || default_source="$1" + pactl --format=json list sources | jq -r ".[] | select(.name == \"$default_source\") | .active_port" | sed 's/\[In] //' +} + +# get the *active port* for output +deviceget() { + [ -z "$1" ] && default_sink="$(pactl get-default-sink)" || default_sink="$1" + pactl --format=json list sinks | jq -r ".[] | select(.name == \"$default_sink\") | .active_port" | sed 's/\[Out] //' +} + +# get the default sink +devicegetdefaultsink() { + pactl get-default-sink +} + +# get the default source +devicegetdefaultsource() { + pactl get-default-source +} + +sourceset() { + pactl set-default-source "$1" +} + +sinkset() { + pactl set-default-sink "$1" +} + +profileset() { + pactl set-card-profile "$1" "$2" +} + +# get a list of sinks +_sinkssubmenu() { + [ -z "$1" ] && default_sink="$(pactl get-default-sink)" || default_sink="$1" + pactl --format=json list sinks | jq -r '.[] | .name, .description' | while read -r line; do + name="$line" + read -r description + if [ "$default_sink" = "$name" ]; then + printf "%s %s %s ^ sinkset %s\n" "$icon_chk" "$icon_spk" "$description" "$name" + else + printf " %s %s ^ sinkset %s\n" "$icon_spk" "$description" "$name" + fi + done +} + +# get a list of output ports +_outportssubmenu() { + [ -z "$1" ] && default_sink="$(pactl get-default-sink)" || default_sink="$1" + active_out_port="$(deviceget "$default_sink")" + pactl --format=json list cards | jq -r ".[] | .ports | to_entries[] | select((.key | startswith(\"[Out] \")) and .value.availability != \"not available\") | .key" | sed 's/\[Out] //' | while read -r line; do + [ "$active_out_port" = "$line" ] && icon="$icon_ton" || icon="$icon_tof" + printf " %s %s ^ deviceset %s +" "$icon" "$line" "$line" + done +} + +# get a list of input sources +_sourcessubmenu() { + [ -z "$1" ] && default_source="$(pactl get-default-source)" || default_source="$1" + pactl --format=json list sources | jq -r '.[] | select (.monitor_source == "") | .name, .description' | while read -r line; do + name="$line" + read -r description + if [ "$default_source" = "$name" ]; then + printf "%s %s %s ^ sourceset %s\n" "$icon_chk" "$icon_mic" "$description" "$name" + else + printf " %s %s ^ sourceset %s\n" "$icon_mic" "$description" "$name" + fi + done +} + +_cardssubmenu() { + cards="$(pactl -f json list cards)" + printf "%s\n" "$cards" | jq -r '.[] | [.name, .active_profile] | @tsv' | \ + while IFS=" " read -r card active_profile; do + printf "%s %s\n" "$icon_nte" "$card" + printf "%s\n" "$cards" | jq -r ".[] | select(.name == \"$card\") | .profiles | to_entries[] | select(.value.available) | [.key, .value.description] | @tsv" | \ + while IFS=" " read -r profile desc; do + if [ "$profile" = "$active_profile" ]; then + printf "%s %s ^ true\n" "$icon_chk" "$desc" + else + printf " %s ^ profileset %s \"%s\"\n" "$desc" "$card" "$profile" + fi + done + done +} + +# get a list of input ports +_inportssubmenu() { + # if the Headset is NOT plugged in, then do not display Headset + # as a option, as clicking on it causes pulse to unset the source!! + [ -z "$1" ] && default_source="$(pactl get-default-source)" || default_source="$1" + active_in_port="$(devicegetinput "$default_source")" + pactl --format=json list sources | jq -r ".[] | select(.name == \"$default_source\" ) | .ports[] | select(.availability != \"not available\" ) | .name" | sed 's/\[In] //' | while read -r line; do + [ "$active_in_port" = "$line" ] && icon="$icon_ton" || icon="$icon_tof" + printf " %s %s ^ devicesetinput %s\n" "$icon" "$line" "$line" + done +} + +_ringmodesubmenu() { + if [ -f "$XDG_CONFIG_HOME"/sxmo/.noring ]; then + printf " %s Ring ^ rm -f \"$XDG_CONFIG_HOME\"/sxmo/.noring\n" "$icon_tof" + else + printf " %s Ring ^ touch \"$XDG_CONFIG_HOME\"/sxmo/.noring\n" "$icon_ton" + fi + if [ -f "$XDG_CONFIG_HOME"/sxmo/.novibrate ]; then + printf " %s Vibrate ^ rm -f \"$XDG_CONFIG_HOME\"/sxmo/.novibrate\n" "$icon_tof" + else + printf " %s Vibrate ^ touch \"$XDG_CONFIG_HOME\"/sxmo/.novibrate\n" "$icon_ton" + fi + if [ -f "$XDG_CONFIG_HOME"/sxmo/.busy ]; then + end="$(cat "$XDG_CONFIG_HOME"/sxmo/.busy)" + if [ -n "$end" ] && [ "$(date +%s)" -gt "$end" ]; then + rm "$XDG_CONFIG_HOME"/sxmo/.busy + fi + fi + if [ -f "$XDG_CONFIG_HOME"/sxmo/.busy ]; then + printf " %s Busy ^ rm -f \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_ton" + else + printf " %s Busy ^ touch \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_tof" + printf " %s Busy for 15 min ^ date '+%%s+15*60' | bc > \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_tmr" + printf " %s Busy for 30 min ^ date '+%%s+30*60' | bc > \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_tmr" + printf " %s Busy for 1 hour ^ date '+%%s+60*60' | bc > \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_tmr" + printf " %s Busy for 2 hour ^ date '+%%s+2*60*60' | bc > \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_tmr" + printf " %s Busy for 3 hour ^ date '+%%s+3*60*60' | bc > \"$XDG_CONFIG_HOME\"/sxmo/.busy\n" "$icon_tmr" + fi +} + +menuchoices() { +cur_vol="$(volget)" +cur_mic_vol="$(micvolget)" +default_sink_name="$(devicegetdefaultsink)" +default_source_name="$(devicegetdefaultsource)" +grep . </dev/null | jq -r .server_name | grep -q PipeWire; then + sxmo_audio.sh device set Earpiece + sxmo_log "Successfully enabled call audio mode (Earpiece)." + sxmo_hook_statusbar.sh volume + return 0 + fi + if ca_dbus_set_prop SelectMode uint32:1; then + sxmo_log "Successfully enabled call audio mode." + sxmo_hook_statusbar.sh volume + else + sxmo_notify_user.sh "Failed to enable call audio mode." + return 1 + fi +} + +disable_call_audio_mode() { + if pactl -f json info 2>/dev/null | jq -r .server_name | grep -q PipeWire; then + sxmo_audio.sh device set Speaker + sxmo_log "Successfully disabled call audio mode (Speaker)." + sxmo_hook_statusbar.sh volume + return 0 + fi + if ca_dbus_set_prop SelectMode uint32:0; then + sxmo_log "Successfully disabled call audio mode." + sxmo_hook_statusbar.sh volume + else + sxmo_notify_user.sh "Failed to disable call audio mode." + return 1 + fi +} + +setup_audio() { + if ! enable_call_audio_mode; then + return 1 + fi +} + +reset_audio() { + if ! disable_call_audio_mode; then + return 1 + fi +} + +"$@" diff --git a/overlay/rootfs/usr/bin/sxmo_ob.sh b/overlay/rootfs/usr/bin/sxmo_ob.sh new file mode 100755 index 0000000..eed11e5 --- /dev/null +++ b/overlay/rootfs/usr/bin/sxmo_ob.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright 2022 Sxmo Contributors + +# This script is responsible for starting wob, or xob, and making sure it exits cleanly + +# include common definitions +# shellcheck source=scripts/core/sxmo_common.sh +. sxmo_common.sh + +export WOB_DISABLE_PLEDGE=1 +ob_sock="$XDG_RUNTIME_DIR"/sxmo.obsock +rm -f "$ob_sock" +mkfifo "$ob_sock" + +# By opening the socket as read-write it isn't closed after the first write +# see https://unix.stackexchange.com/questions/392697 +"${1:-wob}" <> "$ob_sock" & +OBPID=$! + +finish() { + # Only finish once + trap - TERM INT EXIT + kill "$OBPID" + rm -f "$ob_sock" +} +trap 'finish' TERM INT EXIT + +wait "$OBPID" diff --git a/overlay/rootfs/usr/share/alsa/ucm2/msm8916/HiFi.conf b/overlay/rootfs/usr/share/alsa/ucm2/msm8916/HiFi.conf index dfa8da9..4019073 100644 --- a/overlay/rootfs/usr/share/alsa/ucm2/msm8916/HiFi.conf +++ b/overlay/rootfs/usr/share/alsa/ucm2/msm8916/HiFi.conf @@ -1,9 +1,6 @@ -# Use case configuration for common MSM8916 devices. -# All analog outputs/inputs connected to internal PM8916 codec. - Define { - WcdPlaybackPCM "hw:${CardId},0" - WcdCapturePCM "hw:${CardId},1" + WcdPlaybackPCM "hw:${CardId},0" + WcdCapturePCM "hw:${CardId},1" } @@ -12,5 +9,4 @@ Define { - diff --git a/overlay/rootfs/usr/share/alsa/ucm2/platforms/msm8916/qdsp6.conf b/overlay/rootfs/usr/share/alsa/ucm2/platforms/msm8916/qdsp6.conf new file mode 100644 index 0000000..e26a3ab --- /dev/null +++ b/overlay/rootfs/usr/share/alsa/ucm2/platforms/msm8916/qdsp6.conf @@ -0,0 +1,15 @@ +SectionVerb { + EnableSequence [ + cset "name='${var:MultiMedia1Port} Audio Mixer MultiMedia1' 1" + cset "name='MultiMedia2 Mixer ${var:MultiMedia2Port}' 1" + cset "name='${var:VoiceRXPort} Voice Mixer VoiceMMode1' 1" + cset "name='VoiceMMode1 Capture Mixer ${var:VoiceTXPort}' 1" + ] + + DisableSequence [ + cset "name='${var:MultiMedia1Port} Audio Mixer MultiMedia1' 0" + cset "name='MultiMedia2 Mixer ${var:MultiMedia2Port}' 0" + cset "name='${var:VoiceRXPort} Voice Mixer VoiceMMode1' 0" + cset "name='VoiceMMode1 Capture Mixer ${var:VoiceTXPort}' 0" + ] +} diff --git a/overlay/rootfs/usr/share/q6voiced/q6voiced.conf b/overlay/rootfs/usr/share/q6voiced/q6voiced.conf new file mode 100644 index 0000000..88a20b4 --- /dev/null +++ b/overlay/rootfs/usr/share/q6voiced/q6voiced.conf @@ -0,0 +1,2 @@ +q6voice_card=0 +q6voice_device=4 diff --git a/prebuilt/kernel-6.12.1-msm8916/camera/msm8905-cam.ko b/prebuilt/kernel-6.12.1-msm8916/camera/msm8905-cam.ko new file mode 100644 index 0000000000000000000000000000000000000000..ecc2879f2583e3083e8b6ce31fdf21e965ab7125 GIT binary patch literal 17088 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 D$-zmo literal 0 HcmV?d00001 diff --git a/prebuilt/kernel-6.12.1-msm8916/camera/msm8905-cam.ko.zst b/prebuilt/kernel-6.12.1-msm8916/camera/msm8905-cam.ko.zst new file mode 100644 index 0000000000000000000000000000000000000000..677b22bd07064293c96b4c987043d712e3efd301 GIT binary patch literal 7732 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 literal 0 HcmV?d00001 diff --git a/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko b/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko index 4a9058fa44020d8e39dbcfe761324187ec05315b..7430da49a67af247255d018e7998168f45a44ef2 100644 GIT binary patch delta 2292 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 SpYt_)6ueZ){9uvKhyMZ=zcdH{ diff --git a/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst b/prebuilt/kernel-6.12.1-msm8916/leds/leds-msm8905-torch.ko.zst new file mode 100644 index 0000000000000000000000000000000000000000..75024db65ea6b62e018a58198756c06fbfacbdfc GIT binary patch literal 3143 zcmV-N47l?swJ-f-=pD^r0BVxl9#wD_-D(V>;G}>lTx_g=iF;8q?~_W91_2&M1Z%+$ z_&&Ra8-L*j7T|V5pPEZ_GqeiPeHO3D3OF*;9sZ$`pqkX)Xdmf>`SvQJuEcIC@C7Vt7A?E2~@l>$LwDQLmC}y@H|6McGS5R-+R=BLH*Q-H} z|JnX8*5M39bjdb-8G^5V`FbbQqZykt(+ot5+VwSQR@j3+aiPSTC6oVQ6!3J~wUg=1 z$UGs<45j5TT)lK-*n?(zaaaZ6VHKdndc^{w#U1&lm|Qu<4hc759c4GIRCioYGRSiq{kbO9ubVPr5Y&8CSZ8vTeNl z@jCwjyO?Hmz)4xfE+|~dmw`o9ug3&c5ZlPp330`Kkf(zOB*X>9%r;i_dRtHhv5B(} z?Yg4J5F9)w9o9U8hVh(&V9ic?HA9UpIo6+@^k=AXwTs2evHo`PSF+7^`L;*V>uoX5 zrdCkQY~m5PjJwpZ4ioa<<=?;WbKu8DkF*&(XKG~P+?}+uapwA+7hQL|(bKSTfX2<} zzzhxzoRLP(*khvxPtAs==5=?{>ArV&QtF~w2mxKg#*ZG-HzUr?1VY-UMvt``j>e&7 zw2csu#^KmgHFG+4RyKv()IHyg{)hD0+3&i~RJ{()Ws6`_ZFgQXRZXVW4Bu%@oY8j6 zm2GuQ0zeUvI)fWXqfJfM+>J}&GCfZhJwsFGc!2cLE30)joE|A{_)U$rdv26HXGW}Q zuI3CqmD<^iS`H4bU0Ea7j4@LrcFl>wu_p$5W!L{aP_yKXpt-EPh(En3-A>&#{|z8_}U+jdta7KL+1FOx_NC{t8a z)c^f3fiXn=w}1(TsHmgUW#Q6h%)!CExbj7}+WTYvGkqzyravMvM9H9nj2ozQa4-ZA z0+yiZKy`r*Di4PMhtL1;zsmpBe-~8{rXf6?kd|G1)@5HThO$k(9Q4mnT9hwdhf=d{ zHS2Usjz=xWT0TMHkz-9%^1I|6-^D~P`9paJ(F%*^eQMe!M7RR1&x~AVr z=8J)wmPjY7mTTI+rIP8EUvp*BM&H*|&l25gn_JDTo_~!@xI9@^HOrQT-?Dsb;?^9S zestTiZBL~&SC-{@+{%5XNgFNEH7!zeD2prK^aw7?!@0KHD7%(L>^GdcO6A8|4$jRC zn;IG(MJdd#i_uVUeWR$u$wB=(^jvGBVcz$T#>dkypv{X7OZ?-(C zhyn&$WS~i}2_T#ZLc{OENlM^x7C404@iXx7DgPIcISI@6+Tw|y9OVy@5dTXJAq#;; z);ck9p-EyakYrtm|2zD38vft#*B|xQN%%j*UoZUi_CE`Msqh!k{}a9v;*jVbe%}WF z-@jD;`qRIT{`KEy$je{+GOk(v9sFJoNglEuhM@uA0ATD3;Dd!L-|JHWQ$Uih#WPYO za-P{u%lGtVZu38lCxMYRL$j&ry{4_o*zjck1G}G#vB%mN3jhd{w z=1P>sX}HDh?Y5c(64EnjHQG!qUypwU<;RaglB~$zMCNx9(SB?MMN8k(r^5w*J}fIP zGh9SEE+qJ7G1#ZVBFWy$O3R5>m5Uoi;QS>s@8JcEsTxRzG_^QP0mDh8_`{zQ7(zsl z;)e+k8c>Dr;R1$~D$+#QlE8>>Qv9qeipHM>#10om{`auHrT^tp0}lNm`H_SwN5lwK zl6V&Xiz(YLz?8@d_`BiUEp=hogx5{`3<4ZJ?|Y*iR@Mr7jc6NYS2$hTFiqX=kqf`H-%J?Cwm3&7%;z%7Jo_>e~J}X$L%3dDZ^F#(JKgMS#zf3-FAH$eUh z$W~ThR-g>aFXQr$$o}iUCHrRDNh_yLgmO>+28Z1@3)`b}s+;eTm= z8a4i%m><;ti|hwg{vDVf6n`32DxmO5@C|+11r(roSpG}!)5uyeEtnqJYu?HiPH*Lg zO|;FOYYDG8_QaLB)u=4LT^4==f?}vL5;12PQc6ZdWF`ek2hs`{4y1Y5O6ma;0Hr{V zQpzD@lmsbBK~kz8(-?TO#7@x!1tPb9n4I5SK)^dR{YDkRRW3O9I#Tsb@I2oa@O2$` zz~~OskpFzYUtS@%F?3!MM;^Svxa1`lQ?z3n9v6?>=5+z@1^KQ7!WLm)jhPV^hQ zVfY?o07E-4jnWudFXkTe6jBYuI`Ji=OBOuRZp;|Ep*2S1F8;qXSaGljpk&eRYpQ?Fya(J{|p zBCIiuJRN&_ayhhMM%VZG0Iq%z?X#()Jf8XYZE*JSp=+1Lo?02`Nnyv0Yq_fG3>DX$QcBa;~xL^JYtxy#gdwAxnZ#jLh&~khYaR0aJ1o}%S^}jObe(G z8_s{f*9NV@(4F!@!WkD=o@Hxp`FamP>bdrU+WExV^UPsx;hglYL00ZI`u33pvO(<* z{Bco3MBWvud1{9n*}zjxC76Ba0n?<;c^}G|2e3)~&7N(U+8Dt=^YXXs?`0AW^yML% zAJw~F!Rdk?awRu#dZ1}=3noh-!3!bEt#?{!Bb>IewM~xJ(&#A^n;V1{$@9;xKu$MS z!6dV#N`oZ71?M*wp(<}T{*88BCXALeXx?AeG|T1ORT9@}mjbSg*;Nc3%^BNsgu3m+ zMi0dhfXpGCKD4BmY`sVit8eT+rNs3(LBm#nc$R1YGE*b;wvw@zM~4v&PE$wymdw7{ z<3T&m#VQ5{9OlE!!8M~?nv+qx`zbm&!!w~n2=*iHdBC)OF3&($X3D1w($v2H`<3^& zpQ=01jdTYPeBqe}$pTRmr zaVmurv<9AdyCZpYd5IAK2sNw76auQJmRJW2-sRSm>|IzCfNHh?kOk0ATOKPkzxT;@ zj*Aup{k;2ji8@jlQ$dp`qg(2Swg!q_wHGTAp$`3*p0+5mi>M5ju;LQHg1g>h%MgOz^+Tm(|5OWtaZ9Lf?jIpEO96N%Hw7;tp`L+6zx;Xz&KBISBPUAm1w?-EjCN!`@ z{x=+;4c3C5MwCDLVajdy)O~p5=Mid0TDL46uz-7*g~YnL%<`#zhYiMLcMUq}36Vp2 h{E;39AISiJQEM#q9|SztIy-%hnc%g9j|FmO&!EA2Cd&W- literal 0 HcmV?d00001 diff --git a/scripts/apply-bananarchy-overlay.sh b/scripts/apply-bananarchy-overlay.sh index 1fc163f..b48bcf0 100755 --- a/scripts/apply-bananarchy-overlay.sh +++ b/scripts/apply-bananarchy-overlay.sh @@ -43,9 +43,9 @@ echo "[1/4] Installing Bananarchy rootfs overlay (artwork, branding, Sxmo 240x32 rm -f "$TARGET_ROOTFS/usr/bin/bantam" "$TARGET_ROOTFS/usr/local/bin/bantam" "$TARGET_ROOTFS/etc/doas.d/wheel.conf" cp -af "$BASE_DIR/overlay/rootfs/." "$TARGET_ROOTFS/" chown 0:0 "$TARGET_ROOTFS/etc/doas.d/50-sxmo.conf" 2>/dev/null || true -chmod 600 "$TARGET_ROOTFS/etc/doas.d/50-sxmo.conf" 2>/dev/null || true +chmod 640 "$TARGET_ROOTFS/etc/doas.d/50-sxmo.conf" 2>/dev/null || true -echo "[2/4] Installing prebuilt QDSP6/Q6Voice kernel modules, torch LED module, and DTBs..." +echo "[2/4] Installing prebuilt QDSP6/Q6Voice kernel modules, torch LED module, camera module, and DTBs..." if [ -d "$TARGET_ROOTFS/lib/modules/6.12.1-msm8916/kernel/sound/soc/qcom/qdsp6" ]; then cp -af "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/qdsp6/"*.ko* \ "$TARGET_ROOTFS/lib/modules/6.12.1-msm8916/kernel/sound/soc/qcom/qdsp6/" @@ -55,6 +55,12 @@ if [ -d "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/leds" ] && \ cp -af "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/leds/"*.ko* \ "$TARGET_ROOTFS/lib/modules/6.12.1-msm8916/kernel/drivers/leds/" fi +if [ -d "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/camera" ] && \ + [ -d "$TARGET_ROOTFS/lib/modules/6.12.1-msm8916/kernel/drivers" ]; then + mkdir -p "$TARGET_ROOTFS/lib/modules/6.12.1-msm8916/kernel/drivers/media/platform/qcom/camss" + cp -af "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/camera/"*.ko* \ + "$TARGET_ROOTFS/lib/modules/6.12.1-msm8916/kernel/drivers/media/platform/qcom/camss/" +fi mkdir -p "$TARGET_ROOTFS/boot/dtbs" cp -af "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/dtbs/"*.dtb "$TARGET_ROOTFS/boot/dtbs/" cp -af "$BASE_DIR/prebuilt/kernel-6.12.1-msm8916/dtbs/$DTB_FILE" "$TARGET_ROOTFS/boot/$DTB_FILE" diff --git a/scripts/build-kernel-audio.sh b/scripts/build-kernel-audio.sh index 6944fc6..b2ca409 100755 --- a/scripts/build-kernel-audio.sh +++ b/scripts/build-kernel-audio.sh @@ -41,14 +41,22 @@ EOF curl \ tar \ xz \ + zstd \ gcc-armv7 \ g++-armv7 \ binutils-armv7 - mkdir -p /tmp/kbuild + mkdir -p /tmp/kbuild /bananarchy/prebuilt/kernel-6.12.1-msm8916 + CACHE_TAR="/bananarchy/prebuilt/kernel-6.12.1-msm8916/linux-${KERNEL_TAG#v}.tar.gz" cd /tmp/kbuild - echo "[1/5] Downloading Linux ${KERNEL_TAG}..." - curl -sSL "$KERNEL_URL" -o linux.tar.gz + if [ -f "$CACHE_TAR" ]; then + echo "[1/5] Using cached Linux ${KERNEL_TAG} tarball..." + cp "$CACHE_TAR" linux.tar.gz + else + echo "[1/5] Downloading Linux ${KERNEL_TAG}..." + curl -sSL "$KERNEL_URL" -o linux.tar.gz + cp linux.tar.gz "$CACHE_TAR" || true + fi tar -xzf linux.tar.gz cd "linux-${KERNEL_TAG#v}" @@ -60,11 +68,13 @@ EOF make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- olddefconfig make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- modules_prepare - echo "[4/5] Building QDSP6/Q6Voice sound modules, torch LED module, and Nokia MSM8905/MSM8909 DTBs..." + echo "[4/5] Building QDSP6/Q6Voice sound modules, torch LED module, camera module, and Nokia MSM8905/MSM8909 DTBs..." make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- -j"$(nproc)" \ KBUILD_MODPOST_WARN=1 M=sound/soc/qcom/qdsp6 modules make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- -j"$(nproc)" \ KBUILD_MODPOST_WARN=1 M=/bananarchy/src/leds-msm8905-torch modules + make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- -j"$(nproc)" \ + KBUILD_MODPOST_WARN=1 M=/bananarchy/src/msm8905-cam modules make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- -j"$(nproc)" \ qcom/qcom-msm8905-nokia-argon.dtb \ qcom/qcom-msm8909-nokia-sparkler.dtb \ @@ -73,15 +83,27 @@ EOF echo "[5/5] Exporting built modules and DTBs to prebuilt/kernel-6.12.1-msm8916..." mkdir -p /bananarchy/prebuilt/kernel-6.12.1-msm8916/qdsp6 \ /bananarchy/prebuilt/kernel-6.12.1-msm8916/leds \ + /bananarchy/prebuilt/kernel-6.12.1-msm8916/camera \ /bananarchy/prebuilt/kernel-6.12.1-msm8916/dtbs cp -a sound/soc/qcom/qdsp6/*.ko /bananarchy/prebuilt/kernel-6.12.1-msm8916/qdsp6/ cp -a /bananarchy/src/leds-msm8905-torch/leds-msm8905-torch.ko \ /bananarchy/prebuilt/kernel-6.12.1-msm8916/leds/ + cp -a /bananarchy/src/msm8905-cam/msm8905-cam.ko \ + /bananarchy/prebuilt/kernel-6.12.1-msm8916/camera/ armv7-alpine-linux-musleabihf-strip --strip-unneeded \ /bananarchy/prebuilt/kernel-6.12.1-msm8916/qdsp6/*.ko \ - /bananarchy/prebuilt/kernel-6.12.1-msm8916/leds/*.ko + /bananarchy/prebuilt/kernel-6.12.1-msm8916/leds/*.ko \ + /bananarchy/prebuilt/kernel-6.12.1-msm8916/camera/*.ko + for ko in \ + /bananarchy/prebuilt/kernel-6.12.1-msm8916/qdsp6/*.ko \ + /bananarchy/prebuilt/kernel-6.12.1-msm8916/leds/*.ko \ + /bananarchy/prebuilt/kernel-6.12.1-msm8916/camera/*.ko; do + zstd -f -q -k "$ko" -o "${ko}.zst" + done make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- \ M=/bananarchy/src/leds-msm8905-torch clean + make ARCH=arm CROSS_COMPILE=armv7-alpine-linux-musleabihf- \ + M=/bananarchy/src/msm8905-cam clean cp -a arch/arm/boot/dts/qcom/qcom-msm8905-nokia-argon.dtb \ arch/arm/boot/dts/qcom/qcom-msm8909-nokia-sparkler.dtb \ arch/arm/boot/dts/qcom/qcom-msm8909-nokia-leo.dtb \ @@ -92,6 +114,8 @@ EOF dtc -I dts -O dtb -o /bananarchy/prebuilt/kernel-6.12.1-msm8916/dtbs/qcom-msm8905-nokia-coconut.dtb \ /bananarchy/devices/nokia-tough/qcom-msm8905-nokia-coconut.dts 2>/dev/null - chown -R "$HOST_UID:$HOST_GID" /bananarchy/prebuilt/kernel-6.12.1-msm8916 /bananarchy/src/leds-msm8905-torch - echo "Kernel audio modules, torch LED module, and DTBs built successfully." + chown -R "$HOST_UID:$HOST_GID" /bananarchy/prebuilt/kernel-6.12.1-msm8916 \ + /bananarchy/src/leds-msm8905-torch \ + /bananarchy/src/msm8905-cam + echo "Kernel audio modules, torch LED module, camera module, and DTBs built successfully." ' diff --git a/scripts/configure-rootfs-from-scratch.sh b/scripts/configure-rootfs-from-scratch.sh index 78ff0a8..07cdff8 100755 --- a/scripts/configure-rootfs-from-scratch.sh +++ b/scripts/configure-rootfs-from-scratch.sh @@ -73,7 +73,14 @@ configure_common_rootfs() { tcl-lib \ tcl-tls \ sqlite \ - sqlite-tcl + sqlite-tcl \ + modemmanager \ + modemmanager-udev \ + mobile-broadband-provider-info \ + postmarketos-base-ui-modemmanager \ + postmarketos-base-ui-modemmanager-openrc \ + i2c-tools \ + v4l-utils echo " -> Installing busybox and busybox-extras symlinks..." for applet in $(/bin/busybox --list-full); do @@ -218,9 +225,11 @@ EOF sed -i 's|^#ttyS0::respawn:/sbin/getty.*|ttyMSM0::respawn:/sbin/getty -L 115200 ttyMSM0 vt100|' "$ROOTFS/etc/inittab" fi - if ! grep -qx "panel_mipi_dbi" "$ROOTFS/etc/modules" 2>/dev/null; then - echo "panel_mipi_dbi" >> "$ROOTFS/etc/modules" - fi + for mod in panel_mipi_dbi leds_msm8905_torch msm8905-cam; do + if ! grep -qx "$mod" "$ROOTFS/etc/modules" 2>/dev/null; then + echo "$mod" >> "$ROOTFS/etc/modules" + fi + done mkdir -p "$ROOTFS/etc/udev/rules.d" cat > "$ROOTFS/etc/udev/rules.d/90-nokia-argon-leds.rules" < Installing patched audio DTB, 20 QDSP6 kernel modules, UCM, and WirePlumber S16LE rules..." + echo " -> Installing patched audio DTB, QDSP6/torch/camera kernel modules, UCM, and WirePlumber S16LE rules..." cp "$BANANARCHY_DIR/prebuilt/kernel-6.12.1-msm8916/dtbs/qcom-msm8905-nokia-argon.dtb" \ "$ROOTFS/boot/dtbs/qcom-msm8905-nokia-argon.dtb" cp "$BANANARCHY_DIR/prebuilt/kernel-6.12.1-msm8916/qdsp6/"*.ko* \ "$ROOTFS/lib/modules/6.12.1-msm8916/kernel/sound/soc/qcom/qdsp6/" + mkdir -p "$ROOTFS/lib/modules/6.12.1-msm8916/extra" + cp "$BANANARCHY_DIR/prebuilt/kernel-6.12.1-msm8916/leds/"*.ko* \ + "$ROOTFS/lib/modules/6.12.1-msm8916/extra/" 2>/dev/null || true + cp "$BANANARCHY_DIR/prebuilt/kernel-6.12.1-msm8916/camera/"*.ko* \ + "$ROOTFS/lib/modules/6.12.1-msm8916/extra/" 2>/dev/null || true depmod -b "$ROOTFS" 6.12.1-msm8916 sed -i 's/"CS-Voice"/"VoiceMMode1"/g' \ @@ -676,6 +690,7 @@ EOF if [ -x /bananarchy/scripts/apply-bananarchy-overlay.sh ]; then /bananarchy/scripts/apply-bananarchy-overlay.sh "$ROOTFS" nokia-argon + depmod -b "$ROOTFS" 6.12.1-msm8916 fi chown -R 10000:10000 "$ROOTFS/home/user" diff --git a/src/leds-msm8905-torch/leds-msm8905-torch.c b/src/leds-msm8905-torch/leds-msm8905-torch.c index ae2d135..8df07cb 100644 --- a/src/leds-msm8905-torch/leds-msm8905-torch.c +++ b/src/leds-msm8905-torch/leds-msm8905-torch.c @@ -188,20 +188,39 @@ static void msm8905_pon_lookup_regmap(void) { struct device_node *np; struct platform_device *pon_pdev; + struct platform_device *codec_pdev; + struct regmap *codec_regmap = NULL; np = of_find_compatible_node(NULL, NULL, "qcom,pm8916-pon"); - if (!np) - return; + if (np) { + pon_pdev = of_find_device_by_node(np); + of_node_put(np); + if (pon_pdev) { + if (pon_pdev->dev.parent) + pon_regmap = dev_get_regmap(pon_pdev->dev.parent, NULL); + put_device(&pon_pdev->dev); + } + } - pon_pdev = of_find_device_by_node(np); - of_node_put(np); - if (!pon_pdev) - return; - - if (pon_pdev->dev.parent) - pon_regmap = dev_get_regmap(pon_pdev->dev.parent, NULL); - - put_device(&pon_pdev->dev); + /* + * Configure PM8909 WCD analog codec (USID 1): + * - CDC_A_MICB_1_VAL (0xf141) = 0x60 (2.8V micbias for internal MEMS mic) + * - CDC_A_RX_EAR_CTL (0xf1c0) |= 0x20 (+6 dB EAR PA gain for earpiece) + */ + np = of_find_compatible_node(NULL, NULL, "qcom,pm8916-wcd-analog-codec"); + if (np) { + codec_pdev = of_find_device_by_node(np); + of_node_put(np); + if (codec_pdev) { + if (codec_pdev->dev.parent) + codec_regmap = dev_get_regmap(codec_pdev->dev.parent, NULL); + put_device(&codec_pdev->dev); + } + } + if (codec_regmap) { + regmap_write(codec_regmap, 0xf141U, 0x60U); + regmap_update_bits(codec_regmap, 0xf1c0U, 0x20U, 0x20U); + } } static int __init msm8905_torch_init(void) diff --git a/src/msm8905-cam/Makefile b/src/msm8905-cam/Makefile new file mode 100644 index 0000000..4abb00f --- /dev/null +++ b/src/msm8905-cam/Makefile @@ -0,0 +1,10 @@ +obj-m += msm8905-cam.o + +KDIR ?= /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/src/msm8905-cam/msm8905-cam.c b/src/msm8905-cam/msm8905-cam.c new file mode 100644 index 0000000..980d1b8 --- /dev/null +++ b/src/msm8905-cam/msm8905-cam.c @@ -0,0 +1,1013 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Qualcomm MSM8905 / Nokia 8110 4G (nokia-argon) Rear Camera V4L2 Driver + * + * Drives the GalaxyCore GC2395 2MP MIPI CSI-2 sensor on BLSP1 I2C3 (GPIO 29/30) + * through MSM8909 CAMSS (CSIPHY0 -> CSID0 -> ISPIF RDI0 -> VFE0 WM0 -> VBIF) + * and exposes /dev/video0 (800x600 RGB24 V4L2 capture device). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MSM8905_GCC_PHYS 0x01800000UL +#define MSM8905_GCC_SIZE 0x00080000UL +#define MSM8905_TLMM_PHYS 0x01000000UL +#define MSM8905_TLMM_SIZE 0x00070000UL +#define MSM8905_CAMSS_PHYS 0x01b00000UL +#define MSM8905_CAMSS_SIZE 0x00020000UL +#define MSM8905_VBIF_PHYS 0x01b40000UL +#define MSM8905_VBIF_SIZE 0x00001000UL + +#define CAM_RAW_WIDTH 1600U +#define CAM_RAW_HEIGHT 1200U +#define CAM_RAW_STRIDE 2000U +#define CAM_RAW_FRAME_BYTES (CAM_RAW_STRIDE * CAM_RAW_HEIGHT) +#define CAM_DMA_ALLOC_BYTES 0x00280000UL + +#define CAM_OUT_WIDTH 800U +#define CAM_OUT_HEIGHT 600U +#define CAM_OUT_BPP 3U +#define CAM_OUT_STRIDE (CAM_OUT_WIDTH * CAM_OUT_BPP) +#define CAM_OUT_FRAME_BYTES (CAM_OUT_STRIDE * CAM_OUT_HEIGHT) +#define CAM_MAX_MMAP_BUFS 4U + +/* PM8909 USID 1 LDO6 (1.8V VIO/CSI) and LDO17 (2.85V VANA) enable registers */ +#define PM8909_LDO6_EN_CTL 0x4546U +#define PM8909_LDO17_EN_CTL 0x5046U +#define PM8909_REG_EN_BIT 0x80U + +/* PM8909 USID 1 WCD analog codec micbias1 (2.8V) and earpiece PA (+6 dB) */ +#define PM8909_CDC_A_MICB_1_VAL 0xf141U +#define PM8909_CDC_A_RX_EAR_CTL 0xf1c0U + +#define GPIO_CAM_MCLK0 26U +#define GPIO_CAM_I2C_SDA 29U +#define GPIO_CAM_I2C_SCL 30U +#define GPIO_CAM_STANDBY 34U +#define GPIO_CAM_RESET 35U +#define GPIO_CAM_VDIG 58U + +#define CSIPHY0_BASE 0x0ac00U +#define CSID0_BASE 0x08000U +#define ISPIF_BASE 0x0a000U +#define VFE0_BASE 0x10000U + +struct gc2395_reg { + u8 reg; + u8 val; +}; + +static const struct gc2395_reg gc2395_init_regs[] = { + { 0xfe, 0x00 }, { 0xfe, 0x00 }, { 0xfe, 0x00 }, { 0xf2, 0x02 }, + { 0xf4, 0x03 }, { 0xf7, 0x01 }, { 0xf8, 0x28 }, { 0xf9, 0x02 }, + { 0xfa, 0x08 }, { 0xfc, 0x8e }, { 0xe7, 0xcc }, { 0x88, 0x03 }, + { 0x03, 0x04 }, { 0x04, 0x80 }, { 0x05, 0x02 }, { 0x06, 0x86 }, + { 0x07, 0x00 }, { 0x08, 0x20 }, { 0x09, 0x00 }, { 0x0a, 0x04 }, + { 0x0b, 0x00 }, { 0x0c, 0x02 }, { 0x17, 0xd7 }, { 0x18, 0x02 }, + { 0x19, 0x17 }, { 0x1c, 0x18 }, { 0x20, 0x73 }, { 0x22, 0xa2 }, + { 0x21, 0x38 }, { 0x29, 0x20 }, { 0x2f, 0x14 }, { 0x3f, 0x40 }, + { 0xcd, 0x94 }, { 0xce, 0x45 }, { 0xd1, 0x0c }, { 0xd7, 0x9b }, + { 0xd8, 0x99 }, { 0xda, 0x3b }, { 0xd9, 0xb5 }, { 0xdb, 0x75 }, + { 0xe3, 0x1b }, { 0xe4, 0xf8 }, { 0x40, 0x22 }, { 0x43, 0x07 }, + { 0x4e, 0x00 }, { 0x4f, 0x3c }, { 0x68, 0x00 }, { 0xb0, 0x60 }, + { 0xb1, 0x02 }, { 0xb2, 0x80 }, { 0xb6, 0x00 }, { 0x90, 0x01 }, + { 0x92, 0x02 }, { 0x94, 0x06 }, { 0x95, 0x04 }, { 0x96, 0xb0 }, + { 0x97, 0x06 }, { 0x98, 0x40 }, { 0xfe, 0x00 }, { 0xed, 0x00 }, + { 0xfe, 0x03 }, { 0x01, 0x03 }, { 0x02, 0x82 }, { 0x03, 0xd0 }, + { 0x04, 0x04 }, { 0x05, 0x00 }, { 0x15, 0x02 }, { 0x06, 0x80 }, + { 0x11, 0x2b }, { 0x12, 0xd0 }, { 0x13, 0x07 }, { 0x15, 0x00 }, + { 0x1b, 0x10 }, { 0x1c, 0x10 }, { 0x21, 0x08 }, { 0x22, 0x05 }, + { 0x23, 0x13 }, { 0x24, 0x02 }, { 0x25, 0x13 }, { 0x26, 0x06 }, + { 0x29, 0x06 }, { 0x2a, 0x08 }, { 0x2b, 0x06 }, { 0xfe, 0x00 }, +}; + +static const u32 camss_cbcr_offsets[] = { + 0x56004U, 0x5a014U, 0x50004U, + 0x58038U, 0x58044U, 0x58048U, 0x58050U, + 0x4e040U, 0x4e03cU, 0x4e048U, 0x4e050U, 0x4e058U, + 0x4f040U, 0x4f03cU, 0x4f048U, 0x4f050U, 0x4f058U, + 0x4e01cU, 0x52018U, 0x53018U, +}; + +struct msm8905_cam_dev { + struct platform_device *pdev; + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct mutex lock; + + void __iomem *gcc_base; + void __iomem *tlmm_base; + void __iomem *camss_base; + void __iomem *vbif_base; + struct regmap *usid1_regmap; + + u8 *raw_dma_buf; + dma_addr_t raw_dma_phys; + + u8 *mmap_bufs[CAM_MAX_MMAP_BUFS]; + bool buf_queued[CAM_MAX_MMAP_BUFS]; + u32 num_bufs; + u32 sequence; + bool streaming; + bool hw_initialized; +}; + +static struct msm8905_cam_dev *g_cam; + +static inline u32 cam_r32(void __iomem *base, u32 off) +{ + return readl_relaxed(base + off); +} + +static inline void cam_w32(void __iomem *base, u32 off, u32 val) +{ + writel_relaxed(val, base + off); +} + +static void cam_set_rcg(struct msm8905_cam_dev *cam, u32 cmd_off, u32 cfg_val, + u32 m, u32 n, u32 d) +{ + int i; + + cam_w32(cam->gcc_base, cmd_off + 4U, cfg_val); + if (m) { + cam_w32(cam->gcc_base, cmd_off + 8U, m); + cam_w32(cam->gcc_base, cmd_off + 12U, n); + cam_w32(cam->gcc_base, cmd_off + 16U, d); + } + cam_w32(cam->gcc_base, cmd_off, cam_r32(cam->gcc_base, cmd_off) | 1U); + wmb(); + for (i = 0; i < 1000; i++) { + if ((cam_r32(cam->gcc_base, cmd_off) & 1U) == 0U) + break; + udelay(1); + } +} + +static void cam_enable_cbcr(struct msm8905_cam_dev *cam, u32 off) +{ + int i; + + cam_w32(cam->gcc_base, off, cam_r32(cam->gcc_base, off) | 1U); + wmb(); + for (i = 0; i < 1000; i++) { + if ((cam_r32(cam->gcc_base, off) & 0x80000000U) == 0U) + break; + udelay(1); + } +} + +static void cam_gpio_cfg(struct msm8905_cam_dev *cam, u32 gpio, u32 val) +{ + cam_w32(cam->tlmm_base, gpio * 0x1000U, val); +} + +static void cam_gpio_out(struct msm8905_cam_dev *cam, u32 gpio, bool high) +{ + cam_w32(cam->tlmm_base, gpio * 0x1000U + 4U, high ? 2U : 0U); +} + +static bool cam_gpio_in(struct msm8905_cam_dev *cam, u32 gpio) +{ + return (cam_r32(cam->tlmm_base, gpio * 0x1000U + 4U) & 1U) != 0U; +} + +static void cam_i2c_sda_hi(struct msm8905_cam_dev *cam) +{ + cam_gpio_cfg(cam, GPIO_CAM_I2C_SDA, 0x003U); +} + +static void cam_i2c_sda_lo(struct msm8905_cam_dev *cam) +{ + cam_gpio_out(cam, GPIO_CAM_I2C_SDA, false); + cam_gpio_cfg(cam, GPIO_CAM_I2C_SDA, 0x203U); +} + +static void cam_i2c_scl_hi(struct msm8905_cam_dev *cam) +{ + cam_gpio_cfg(cam, GPIO_CAM_I2C_SCL, 0x003U); +} + +static void cam_i2c_scl_lo(struct msm8905_cam_dev *cam) +{ + cam_gpio_out(cam, GPIO_CAM_I2C_SCL, false); + cam_gpio_cfg(cam, GPIO_CAM_I2C_SCL, 0x203U); +} + +static void cam_i2c_delay(struct msm8905_cam_dev *cam) +{ + udelay(2); +} + +static void cam_i2c_start(struct msm8905_cam_dev *cam) +{ + cam_i2c_sda_hi(cam); + cam_i2c_scl_hi(cam); + cam_i2c_delay(cam); + cam_i2c_sda_lo(cam); + cam_i2c_delay(cam); + cam_i2c_scl_lo(cam); + cam_i2c_delay(cam); +} + +static void cam_i2c_stop(struct msm8905_cam_dev *cam) +{ + cam_i2c_sda_lo(cam); + cam_i2c_delay(cam); + cam_i2c_scl_hi(cam); + cam_i2c_delay(cam); + cam_i2c_sda_hi(cam); + cam_i2c_delay(cam); +} + +static bool cam_i2c_tx(struct msm8905_cam_dev *cam, u8 byte) +{ + int i; + bool ack; + + for (i = 7; i >= 0; i--) { + if ((byte >> i) & 1U) + cam_i2c_sda_hi(cam); + else + cam_i2c_sda_lo(cam); + cam_i2c_delay(cam); + cam_i2c_scl_hi(cam); + cam_i2c_delay(cam); + cam_i2c_scl_lo(cam); + } + cam_i2c_sda_hi(cam); + cam_i2c_delay(cam); + cam_i2c_scl_hi(cam); + cam_i2c_delay(cam); + ack = !cam_gpio_in(cam, GPIO_CAM_I2C_SDA); + cam_i2c_scl_lo(cam); + cam_i2c_delay(cam); + return ack; +} + +static u8 cam_i2c_rx(struct msm8905_cam_dev *cam, bool ack) +{ + int i; + u8 val = 0; + + cam_i2c_sda_hi(cam); + for (i = 7; i >= 0; i--) { + cam_i2c_delay(cam); + cam_i2c_scl_hi(cam); + cam_i2c_delay(cam); + if (cam_gpio_in(cam, GPIO_CAM_I2C_SDA)) + val |= (u8)(1U << i); + cam_i2c_scl_lo(cam); + } + if (ack) + cam_i2c_sda_lo(cam); + else + cam_i2c_sda_hi(cam); + cam_i2c_delay(cam); + cam_i2c_scl_hi(cam); + cam_i2c_delay(cam); + cam_i2c_scl_lo(cam); + cam_i2c_delay(cam); + cam_i2c_sda_hi(cam); + return val; +} + +static u8 gc2395_read_reg(struct msm8905_cam_dev *cam, u8 reg) +{ + u8 val; + + cam_i2c_start(cam); + cam_i2c_tx(cam, 0x6eU); + cam_i2c_tx(cam, reg); + cam_i2c_start(cam); + cam_i2c_tx(cam, 0x6fU); + val = cam_i2c_rx(cam, false); + cam_i2c_stop(cam); + return val; +} + +static void gc2395_write_reg(struct msm8905_cam_dev *cam, u8 reg, u8 val) +{ + cam_i2c_start(cam); + cam_i2c_tx(cam, 0x6eU); + cam_i2c_tx(cam, reg); + cam_i2c_tx(cam, val); + cam_i2c_stop(cam); +} + +static int msm8905_cam_hw_init(struct msm8905_cam_dev *cam) +{ + size_t i; + u16 chip_id; + u32 r; + + /* 1. Ensure PM8909 LDO6 (1.8V) and LDO17 (2.85V) are enabled */ + if (cam->usid1_regmap) { + regmap_update_bits(cam->usid1_regmap, PM8909_LDO6_EN_CTL, + PM8909_REG_EN_BIT, PM8909_REG_EN_BIT); + regmap_update_bits(cam->usid1_regmap, PM8909_LDO17_EN_CTL, + PM8909_REG_EN_BIT, PM8909_REG_EN_BIT); + regmap_write(cam->usid1_regmap, PM8909_CDC_A_MICB_1_VAL, 0x60U); + regmap_update_bits(cam->usid1_regmap, PM8909_CDC_A_RX_EAR_CTL, 0x20U, 0x20U); + usleep_range(2000, 3000); + } + + /* 2. Power on VFE GDSC and SMMU vote clocks */ + cam_w32(cam->gcc_base, 0x58034U, cam_r32(cam->gcc_base, 0x58034U) & ~1U); + usleep_range(1000, 2000); + cam_w32(cam->gcc_base, 0x4500cU, cam_r32(cam->gcc_base, 0x4500cU) | 0x3202U); + usleep_range(1000, 2000); + + /* 3. Configure CAMSS root clock generators */ + cam_set_rcg(cam, 0x5a000U, 0x0113U, 0, 0, 0); + cam_set_rcg(cam, 0x58000U, 0x0105U, 0, 0, 0); + cam_set_rcg(cam, 0x4e020U, 0x0107U, 0, 0, 0); + cam_set_rcg(cam, 0x4f020U, 0x0107U, 0, 0, 0); + cam_set_rcg(cam, 0x4e000U, 0x0107U, 0, 0, 0); + cam_set_rcg(cam, 0x52000U, 0x2101U, 1U, 0xffdfU, 0xffdeU); + + for (i = 0; i < ARRAY_SIZE(camss_cbcr_offsets); i++) + cam_enable_cbcr(cam, camss_cbcr_offsets[i]); + + /* 4. Power on GC2395 sensor */ + cam_gpio_cfg(cam, GPIO_CAM_STANDBY, 0x200U); + cam_gpio_cfg(cam, GPIO_CAM_RESET, 0x200U); + cam_gpio_cfg(cam, GPIO_CAM_VDIG, 0x200U); + cam_gpio_out(cam, GPIO_CAM_STANDBY, false); + cam_gpio_out(cam, GPIO_CAM_RESET, false); + cam_gpio_out(cam, GPIO_CAM_VDIG, true); + cam_gpio_cfg(cam, GPIO_CAM_MCLK0, 0x00c4U); + usleep_range(10000, 12000); + cam_gpio_out(cam, GPIO_CAM_STANDBY, true); + cam_gpio_out(cam, GPIO_CAM_RESET, true); + usleep_range(15000, 18000); + + chip_id = ((u16)gc2395_read_reg(cam, 0xf0U) << 8) | gc2395_read_reg(cam, 0xf1U); + if (chip_id != 0x2395U) { + dev_err(&cam->pdev->dev, "GC2395 chip ID mismatch: 0x%04x\n", chip_id); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(gc2395_init_regs); i++) + gc2395_write_reg(cam, gc2395_init_regs[i].reg, gc2395_init_regs[i].val); + + /* 5. CSI_CLK_MUX */ + cam_w32(cam->camss_base, 0x0030U, 0x0U); + cam_w32(cam->camss_base, 0x0020U, 0x0U); + cam_w32(cam->camss_base, 0x0024U, 0x0U); + cam_w32(cam->camss_base, 0x0028U, 0x0U); + + /* 6. CSIPHY0 (qcom,csiphy-v3.1) */ + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x140U, 0x1U); + usleep_range(5000, 6000); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x140U, 0x0U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1ecU, 0x1U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1f4U, 0x1U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x144U, 0x7U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x004U, 0x10U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x008U, 0x14U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1acU, 0x3fU); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1ccU, 0x3fU); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x044U, 0x10U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x048U, 0x14U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1b0U, 0x3fU); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1d0U, 0x3fU); + for (i = 0; i < 4; i++) + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1ccU + 4U * i, 0xffU); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x164U, 0x1U); + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x164U, 0x0U); + for (i = 0; i < 4; i++) + cam_w32(cam->camss_base, CSIPHY0_BASE + 0x1ccU + 4U * i, 0x0U); + + /* 7. CSID0 (qcom,csid-v3.1) */ + cam_w32(cam->camss_base, CSID0_BASE + 0x64U, 0xffffffffU); + cam_w32(cam->camss_base, CSID0_BASE + 0x0cU, 0x7fffU); + usleep_range(5000, 6000); + cam_w32(cam->camss_base, CSID0_BASE + 0x60U, 0xffffffffU); + cam_w32(cam->camss_base, CSID0_BASE + 0x64U, 0xffffffffU); + cam_w32(cam->camss_base, CSID0_BASE + 0x04U, 0x00043200U); + cam_w32(cam->camss_base, CSID0_BASE + 0x08U, 0x0000000fU); + cam_w32(cam->camss_base, CSID0_BASE + 0x10U, 0x0000002bU); + cam_w32(cam->camss_base, CSID0_BASE + 0x20U, (2U << 4) | 0x3U); + + /* 8. VFE0 (qcom,vfe32 v3.6.0) + VBIF */ + cam_w32(cam->camss_base, VFE0_BASE + 0x01cU, 0x00000000U); + cam_w32(cam->camss_base, VFE0_BASE + 0x020U, 0x00800000U); + cam_w32(cam->camss_base, VFE0_BASE + 0x024U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x028U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x018U, 0x00000001U); + cam_w32(cam->camss_base, VFE0_BASE + 0x004U, 0x000003ffU); + usleep_range(5000, 6000); + + for (r = 0x7bcU; r <= 0x7d4U; r += 4U) + cam_w32(cam->camss_base, VFE0_BASE + r, 0xaaa5aaa5U); + cam_w32(cam->camss_base, VFE0_BASE + 0x798U, 0x00010000U); + for (r = 0x7d8U; r < 0x810U; r += 4U) + cam_w32(cam->camss_base, VFE0_BASE + r, 0xcccc1111U); + cam_w32(cam->camss_base, VFE0_BASE + 0x810U, 0x00000103U); + cam_w32(cam->vbif_base, 0x004U, 0x00000001U); + + cam_w32(cam->camss_base, VFE0_BASE + 0x00cU, 0x07ffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x03cU, 0x00000009U); + cam_w32(cam->camss_base, VFE0_BASE + 0x024U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x028U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x018U, 0x00000001U); + cam_w32(cam->camss_base, VFE0_BASE + 0x01cU, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x020U, 0xffffffffU); + + /* 9. ISPIF */ + cam_w32(cam->camss_base, ISPIF_BASE + 0x008U, 0x00001ff9U); + usleep_range(5000, 6000); + cam_w32(cam->camss_base, ISPIF_BASE + 0x008U, 0x00000000U); + usleep_range(5000, 6000); + cam_w32(cam->camss_base, ISPIF_BASE + 0x200U, 0x00000040U); + cam_w32(cam->camss_base, ISPIF_BASE + 0x230U, 0xffffffffU); + cam_w32(cam->camss_base, ISPIF_BASE + 0x234U, 0xffffffffU); + cam_w32(cam->camss_base, ISPIF_BASE + 0x238U, 0xffffffffU); + cam_w32(cam->camss_base, ISPIF_BASE + 0x01cU, 0x00000001U); + cam_w32(cam->camss_base, ISPIF_BASE + 0x208U, 0xffffffffU); + cam_w32(cam->camss_base, ISPIF_BASE + 0x20cU, 0xffffffffU); + cam_w32(cam->camss_base, ISPIF_BASE + 0x210U, 0xffffffffU); + cam_w32(cam->camss_base, ISPIF_BASE + 0x244U, 0x00000000U); + cam_w32(cam->camss_base, ISPIF_BASE + 0x264U, 0x00000001U); + cam_w32(cam->camss_base, ISPIF_BASE + 0x248U, 0xaaaaaaaaU); + wmb(); + + cam->hw_initialized = true; + return 0; +} + +static void msm8905_cam_demosaic(const u8 *raw, u8 *rgb) +{ + u32 y, x; + u32 idx = 0; + + for (y = 0; y < CAM_OUT_HEIGHT; y++) { + const u8 *r0 = raw + (2U * y) * CAM_RAW_STRIDE; + const u8 *r1 = raw + (2U * y + 1U) * CAM_RAW_STRIDE; + + for (x = 0; x < CAM_OUT_WIDTH; x += 2U) { + u32 g_off = (x >> 1) * 5U; + u8 b0 = r0[g_off + 0U]; + u8 g0 = (u8)(((u32)r0[g_off + 1U] + (u32)r1[g_off + 0U]) >> 1); + u8 r_0 = r1[g_off + 1U]; + u8 b1 = r0[g_off + 2U]; + u8 g1 = (u8)(((u32)r0[g_off + 3U] + (u32)r1[g_off + 2U]) >> 1); + u8 r_1 = r1[g_off + 3U]; + + rgb[idx + 0U] = r_0; + rgb[idx + 1U] = g0; + rgb[idx + 2U] = b0; + rgb[idx + 3U] = r_1; + rgb[idx + 4U] = g1; + rgb[idx + 5U] = b1; + idx += 6U; + } + } +} + +static int msm8905_cam_capture_rgb(struct msm8905_cam_dev *cam, u8 *dst_rgb) +{ + int ret; + int poll_cnt; + u32 irq0; + + if (!cam->hw_initialized) { + ret = msm8905_cam_hw_init(cam); + if (ret) + return ret; + } + + /* Program WM0 PING & PONG to raw_dma_phys and arm single frame */ + cam_w32(cam->camss_base, VFE0_BASE + 0x050U, (u32)cam->raw_dma_phys); + cam_w32(cam->camss_base, VFE0_BASE + 0x054U, (u32)cam->raw_dma_phys); + cam_w32(cam->camss_base, VFE0_BASE + 0x058U, 0x00000317U); + cam_w32(cam->camss_base, VFE0_BASE + 0x060U, 0x00000033U); + cam_w32(cam->camss_base, VFE0_BASE + 0x040U, 0x000000a0U); + cam_w32(cam->camss_base, VFE0_BASE + 0x034U, 0x00000001U); + cam_w32(cam->camss_base, VFE0_BASE + 0x04cU, 0x00000003U); + cam_w32(cam->camss_base, VFE0_BASE + 0x038U, 0x00000001U); + cam_w32(cam->camss_base, VFE0_BASE + 0x6fcU, 0x00010004U); + cam_w32(cam->camss_base, VFE0_BASE + 0x024U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x028U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x018U, 0x00000001U); + cam_w32(cam->camss_base, VFE0_BASE + 0x260U, 0x00000002U); + + /* Enable ISPIF RDI0 CID0 on frame boundary and start GC2395 MIPI stream */ + cam_w32(cam->camss_base, ISPIF_BASE + 0x248U, 0xfffffdffU); + wmb(); + + gc2395_write_reg(cam, 0xfeU, 0x00U); + gc2395_write_reg(cam, 0xedU, 0x90U); + gc2395_write_reg(cam, 0xfeU, 0x00U); + + /* Wait for WM0 PING_PONG_IRQ (bit 6 = 0x40) */ + irq0 = 0; + for (poll_cnt = 0; poll_cnt < 150; poll_cnt++) { + irq0 = cam_r32(cam->camss_base, VFE0_BASE + 0x02cU); + if (irq0 & 0x00000040U) + break; + usleep_range(2000, 3000); + } + + /* Immediately stop WM0, ISPIF RDI0, and GC2395 stream to prevent overflow */ + cam_w32(cam->camss_base, ISPIF_BASE + 0x248U, 0xaaaaaaaaU); + cam_w32(cam->camss_base, VFE0_BASE + 0x04cU, 0x00000000U); + cam_w32(cam->camss_base, VFE0_BASE + 0x024U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x028U, 0xffffffffU); + cam_w32(cam->camss_base, VFE0_BASE + 0x018U, 0x00000001U); + wmb(); + + gc2395_write_reg(cam, 0xfeU, 0x00U); + gc2395_write_reg(cam, 0xedU, 0x00U); + + if (!(irq0 & 0x00000040U)) { + cam->hw_initialized = false; + return -ETIMEDOUT; + } + + msm8905_cam_demosaic(cam->raw_dma_buf, dst_rgb); + cam->sequence++; + return 0; +} + +/* V4L2 ioctl operations */ +static int msm8905_cam_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, "msm8905-cam", sizeof(cap->driver)); + strscpy(cap->card, "Nokia 8110 4G Rear Camera", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:msm8905-cam", sizeof(cap->bus_info)); + return 0; +} + +static int msm8905_cam_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index > 0) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_RGB24; + return 0; +} + +static void msm8905_cam_fill_pix_format(struct v4l2_pix_format *pix) +{ + pix->width = CAM_OUT_WIDTH; + pix->height = CAM_OUT_HEIGHT; + pix->pixelformat = V4L2_PIX_FMT_RGB24; + pix->field = V4L2_FIELD_NONE; + pix->bytesperline = CAM_OUT_STRIDE; + pix->sizeimage = CAM_OUT_FRAME_BYTES; + pix->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int msm8905_cam_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + msm8905_cam_fill_pix_format(&f->fmt.pix); + return 0; +} + +static int msm8905_cam_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + msm8905_cam_fill_pix_format(&f->fmt.pix); + return 0; +} + +static int msm8905_cam_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + msm8905_cam_fill_pix_format(&f->fmt.pix); + return 0; +} + +static int msm8905_cam_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index > 0 || fsize->pixel_format != V4L2_PIX_FMT_RGB24) + return -EINVAL; + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = CAM_OUT_WIDTH; + fsize->discrete.height = CAM_OUT_HEIGHT; + return 0; +} + +static int msm8905_cam_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + if (fival->index > 0 || fival->pixel_format != V4L2_PIX_FMT_RGB24 || + fival->width != CAM_OUT_WIDTH || fival->height != CAM_OUT_HEIGHT) + return -EINVAL; + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete.numerator = 1; + fival->discrete.denominator = 15; + return 0; +} + +static int msm8905_cam_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index > 0) + return -EINVAL; + strscpy(inp->name, "GC2395 Rear Camera", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + return 0; +} + +static int msm8905_cam_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int msm8905_cam_s_input(struct file *file, void *priv, unsigned int i) +{ + return (i == 0) ? 0 : -EINVAL; +} + +static int msm8905_cam_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + sp->parm.capture.readbuffers = 2; + sp->parm.capture.timeperframe.numerator = 1; + sp->parm.capture.timeperframe.denominator = 15; + return 0; +} + +static int msm8905_cam_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *sp) +{ + return msm8905_cam_g_parm(file, priv, sp); +} + +static int msm8905_cam_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + u32 i; + + if (rb->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + rb->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + if (rb->count == 0) { + cam->num_bufs = 0; + for (i = 0; i < CAM_MAX_MMAP_BUFS; i++) + cam->buf_queued[i] = false; + return 0; + } + + rb->count = clamp_t(u32, rb->count, 1U, CAM_MAX_MMAP_BUFS); + cam->num_bufs = rb->count; + for (i = 0; i < CAM_MAX_MMAP_BUFS; i++) + cam->buf_queued[i] = false; + return 0; +} + +static int msm8905_cam_querybuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + u32 aligned_size = PAGE_ALIGN(CAM_OUT_FRAME_BYTES); + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || b->index >= cam->num_bufs) + return -EINVAL; + + b->bytesused = CAM_OUT_FRAME_BYTES; + b->flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + if (cam->buf_queued[b->index]) + b->flags |= V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE; + b->field = V4L2_FIELD_NONE; + b->memory = V4L2_MEMORY_MMAP; + b->m.offset = b->index * aligned_size; + b->length = CAM_OUT_FRAME_BYTES; + return 0; +} + +static int msm8905_cam_qbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + b->memory != V4L2_MEMORY_MMAP || b->index >= cam->num_bufs) + return -EINVAL; + + cam->buf_queued[b->index] = true; + b->flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | + V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + return 0; +} + +static int msm8905_cam_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + u32 idx = 0; + int ret; + + if (b->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + b->memory != V4L2_MEMORY_MMAP) + return -EINVAL; + + for (idx = 0; idx < cam->num_bufs; idx++) { + if (cam->buf_queued[idx]) + break; + } + if (idx >= cam->num_bufs) + return -EINVAL; + + ret = msm8905_cam_capture_rgb(cam, cam->mmap_bufs[idx]); + if (ret) + return ret; + + cam->buf_queued[idx] = false; + b->index = idx; + b->bytesused = CAM_OUT_FRAME_BYTES; + b->flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + b->field = V4L2_FIELD_NONE; + b->sequence = cam->sequence; + b->memory = V4L2_MEMORY_MMAP; + b->length = CAM_OUT_FRAME_BYTES; + ktime_get_ts64((struct timespec64 *)&b->timestamp); + return 0; +} + +static int msm8905_cam_streamon(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + cam->streaming = true; + return msm8905_cam_hw_init(cam); +} + +static int msm8905_cam_streamoff(struct file *file, void *priv, + enum v4l2_buf_type i) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + u32 idx; + + if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + cam->streaming = false; + for (idx = 0; idx < CAM_MAX_MMAP_BUFS; idx++) + cam->buf_queued[idx] = false; + return 0; +} + +static const struct v4l2_ioctl_ops msm8905_cam_ioctl_ops = { + .vidioc_querycap = msm8905_cam_querycap, + .vidioc_enum_fmt_vid_cap = msm8905_cam_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = msm8905_cam_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = msm8905_cam_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = msm8905_cam_s_fmt_vid_cap, + .vidioc_enum_framesizes = msm8905_cam_enum_framesizes, + .vidioc_enum_frameintervals = msm8905_cam_enum_frameintervals, + .vidioc_enum_input = msm8905_cam_enum_input, + .vidioc_g_input = msm8905_cam_g_input, + .vidioc_s_input = msm8905_cam_s_input, + .vidioc_g_parm = msm8905_cam_g_parm, + .vidioc_s_parm = msm8905_cam_s_parm, + .vidioc_reqbufs = msm8905_cam_reqbufs, + .vidioc_querybuf = msm8905_cam_querybuf, + .vidioc_qbuf = msm8905_cam_qbuf, + .vidioc_dqbuf = msm8905_cam_dqbuf, + .vidioc_streamon = msm8905_cam_streamon, + .vidioc_streamoff = msm8905_cam_streamoff, +}; + +static ssize_t msm8905_cam_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + int ret; + + if ( mutex_lock_interruptible(&cam->lock) ) + return -ERESTARTSYS; + + if (*ppos == 0 || *ppos >= CAM_OUT_FRAME_BYTES) { + ret = msm8905_cam_capture_rgb(cam, cam->mmap_bufs[0]); + if (ret) { + mutex_unlock(&cam->lock); + return ret; + } + *ppos = 0; + } + + if (count > CAM_OUT_FRAME_BYTES - *ppos) + count = CAM_OUT_FRAME_BYTES - *ppos; + + if (copy_to_user(buf, cam->mmap_bufs[0] + *ppos, count)) { + mutex_unlock(&cam->lock); + return -EFAULT; + } + + *ppos += count; + mutex_unlock(&cam->lock); + return count; +} + +static int msm8905_cam_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct msm8905_cam_dev *cam = video_drvdata(file); + u32 aligned_size = PAGE_ALIGN(CAM_OUT_FRAME_BYTES); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + u32 idx = offset / aligned_size; + + if (idx >= CAM_MAX_MMAP_BUFS || (offset % aligned_size) != 0) + return -EINVAL; + + return remap_vmalloc_range(vma, cam->mmap_bufs[idx], 0); +} + +static __poll_t msm8905_cam_poll(struct file *file, poll_table *wait) +{ + return EPOLLIN | EPOLLRDNORM; +} + +static const struct v4l2_file_operations msm8905_cam_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = v4l2_fh_release, + .read = msm8905_cam_read, + .mmap = msm8905_cam_mmap, + .poll = msm8905_cam_poll, + .unlocked_ioctl = video_ioctl2, +}; + +static void msm8905_cam_lookup_usid1_regmap(struct msm8905_cam_dev *cam) +{ + struct device_node *np; + struct platform_device *codec_pdev; + + np = of_find_compatible_node(NULL, NULL, "qcom,pm8916-wcd-analog-codec"); + if (!np) + return; + + codec_pdev = of_find_device_by_node(np); + of_node_put(np); + if (!codec_pdev) + return; + + if (codec_pdev->dev.parent) + cam->usid1_regmap = dev_get_regmap(codec_pdev->dev.parent, NULL); + + put_device(&codec_pdev->dev); +} + +static int __init msm8905_cam_init(void) +{ + struct msm8905_cam_dev *cam; + u32 aligned_size = PAGE_ALIGN(CAM_OUT_FRAME_BYTES); + int ret; + u32 i; + + if (!of_machine_is_compatible("qcom,msm8905") && + !of_machine_is_compatible("qcom,msm8909") && + !of_machine_is_compatible("nokia,argon")) { + return -ENODEV; + } + + cam = kzalloc(sizeof(*cam), GFP_KERNEL); + if (!cam) + return -ENOMEM; + + mutex_init(&cam->lock); + + cam->pdev = platform_device_register_simple("msm8905-cam", -1, NULL, 0); + if (IS_ERR(cam->pdev)) { + ret = PTR_ERR(cam->pdev); + kfree(cam); + return ret; + } + + dma_coerce_mask_and_coherent(&cam->pdev->dev, DMA_BIT_MASK(32)); + cam->raw_dma_buf = dma_alloc_coherent(&cam->pdev->dev, CAM_DMA_ALLOC_BYTES, + &cam->raw_dma_phys, GFP_KERNEL); + if (!cam->raw_dma_buf) { + ret = -ENOMEM; + goto err_pdev; + } + + for (i = 0; i < CAM_MAX_MMAP_BUFS; i++) { + cam->mmap_bufs[i] = vmalloc_user(aligned_size); + if (!cam->mmap_bufs[i]) { + ret = -ENOMEM; + goto err_vmalloc; + } + } + + cam->gcc_base = ioremap(MSM8905_GCC_PHYS, MSM8905_GCC_SIZE); + cam->tlmm_base = ioremap(MSM8905_TLMM_PHYS, MSM8905_TLMM_SIZE); + cam->camss_base = ioremap(MSM8905_CAMSS_PHYS, MSM8905_CAMSS_SIZE); + cam->vbif_base = ioremap(MSM8905_VBIF_PHYS, MSM8905_VBIF_SIZE); + if (!cam->gcc_base || !cam->tlmm_base || !cam->camss_base || !cam->vbif_base) { + ret = -ENOMEM; + goto err_iomap; + } + + msm8905_cam_lookup_usid1_regmap(cam); + + strscpy(cam->v4l2_dev.name, "msm8905-cam", sizeof(cam->v4l2_dev.name)); + ret = v4l2_device_register(&cam->pdev->dev, &cam->v4l2_dev); + if (ret) + goto err_iomap; + + strscpy(cam->vdev.name, "msm8905-cam", sizeof(cam->vdev.name)); + cam->vdev.v4l2_dev = &cam->v4l2_dev; + cam->vdev.fops = &msm8905_cam_fops; + cam->vdev.ioctl_ops = &msm8905_cam_ioctl_ops; + cam->vdev.release = video_device_release_empty; + cam->vdev.lock = &cam->lock; + cam->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + video_set_drvdata(&cam->vdev, cam); + + ret = video_register_device(&cam->vdev, VFL_TYPE_VIDEO, 0); + if (ret) + goto err_v4l2; + + g_cam = cam; + pr_info("msm8905-cam: registered /dev/video%d (GC2395 800x600 RGB24, dma=0x%08llx)\n", + cam->vdev.num, (unsigned long long)cam->raw_dma_phys); + return 0; + +err_v4l2: + v4l2_device_unregister(&cam->v4l2_dev); +err_iomap: + if (cam->vbif_base) + iounmap(cam->vbif_base); + if (cam->camss_base) + iounmap(cam->camss_base); + if (cam->tlmm_base) + iounmap(cam->tlmm_base); + if (cam->gcc_base) + iounmap(cam->gcc_base); +err_vmalloc: + for (i = 0; i < CAM_MAX_MMAP_BUFS; i++) { + if (cam->mmap_bufs[i]) + vfree(cam->mmap_bufs[i]); + } + dma_free_coherent(&cam->pdev->dev, CAM_DMA_ALLOC_BYTES, + cam->raw_dma_buf, cam->raw_dma_phys); +err_pdev: + platform_device_unregister(cam->pdev); + kfree(cam); + return ret; +} + +static void __exit msm8905_cam_exit(void) +{ + struct msm8905_cam_dev *cam = g_cam; + u32 i; + + if (!cam) + return; + + video_unregister_device(&cam->vdev); + v4l2_device_unregister(&cam->v4l2_dev); + iounmap(cam->vbif_base); + iounmap(cam->camss_base); + iounmap(cam->tlmm_base); + iounmap(cam->gcc_base); + for (i = 0; i < CAM_MAX_MMAP_BUFS; i++) + vfree(cam->mmap_bufs[i]); + dma_free_coherent(&cam->pdev->dev, CAM_DMA_ALLOC_BYTES, + cam->raw_dma_buf, cam->raw_dma_phys); + platform_device_unregister(cam->pdev); + kfree(cam); + g_cam = NULL; +} + +module_init(msm8905_cam_init); +module_exit(msm8905_cam_exit); + +MODULE_DESCRIPTION("Qualcomm MSM8905 / Nokia 8110 4G GC2395 V4L2 Camera Driver"); +MODULE_AUTHOR("Bananarchy Project"); +MODULE_LICENSE("GPL");