Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
317 changes: 235 additions & 82 deletions drivers/intel-hda.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@
(defconstant +dmap-entry-size+ 4)
(defconstant +bdl-entry-size+ 16)
(defconstant +max-bdl-entries+ 256)
(defconstant +playback-period-count+ 3)

(defparameter *hda-min-period-bytes* 256
"Minimum size of each HDA audio period in bytes. Must be a multiple of 128.")

(defparameter *i-want-garbage* nil
"When true, skip the startup silence delay. The first ~100ms of audio
may be garbled while the host audio backend initializes.")

(defparameter *hda-startup-periods* 90
"Number of periods to run silently at startup before unmuting.
The DMA buffer is pre-filled and the SRC adapts during this window.
Total startup silence = *hda-startup-periods* × (period-bytes / 176400) seconds.")

(defparameter *hda-idle-periods* 12
"Number of silent periods after the sink drains before stopping the stream.
Higher values keep the HDA initialized longer, avoiding startup delays on
the next playback at the cost of keeping the IRQ active.")

(defconstant +corb-offset+ 0)
(defconstant +rirb-offset+ (+ +corb-offset+ +corb-max-size+))
Expand All @@ -221,7 +239,9 @@
(irq :initarg :irq :accessor hda-irq)
(dma-buffer-phys :initarg :dma-buffer-phys :accessor hda-dma-buffer-phys)
(dma-buffer-virt :initarg :dma-buffer-virt :accessor hda-dma-buffer-virt)
(dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size))
(dma-buffer-size :initarg :dma-buffer-size :accessor hda-dma-buffer-size)
(period-bytes :initarg :period-bytes :accessor hda-period-bytes)
(stable-period-bytes :initform nil :accessor hda-stable-period-bytes))
(:default-initargs :codecs (make-array 15 :initial-element nil)))

(define-condition device-disconnect () ())
Expand Down Expand Up @@ -841,6 +861,9 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED."))
(setf (hda-irq hda) (mezzano.supervisor:make-simple-irq (pci:pci-intr-line device)))
(format t "Found Intel HDA controller at ~S.~%" device)
(setf (pci:pci-bus-master-enabled device) t)
;; Clear TCSEL PCI register bits 0-2 to fix playback static on ICH.
(let ((tcel (pci:pci-config/8 device #x44)))
(setf (pci:pci-config/8 device #x44) (logand tcel #b11111000)))
;; Perform a controller reset by pulsing crst to 0.
(format t "Begin reset.~%")
(setf (global-reg/32 hda +gctl+) 0)
Expand Down Expand Up @@ -929,8 +952,11 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED."))
(setf (sd-reg/32 hda stream-id +sdnbdpl+) (ldb (byte 32 0) bdl)
(sd-reg/32 hda stream-id +sdnbdpu+) (ldb (byte 32 32) bdl))
(setf (sd-reg/16 hda stream-id +sdnfmt+) #x4011
(sd-reg/16 hda stream-id +sdnlvi+) bdl-length
(sd-reg/32 hda stream-id +sdncbl+) cb-length)))
;; LVI is the last valid descriptor index, not the descriptor count.
(sd-reg/16 hda stream-id +sdnlvi+) (1- bdl-length)
(sd-reg/32 hda stream-id +sdncbl+) cb-length)
;; Poll FIFO size until non-zero (FIFO ready after format is set).
(loop while (zerop (sd-reg/16 hda stream-id +sdnfifos+)))))

(defun stream-reset (hda stream-id)
;; Clear the run bit before doing anything.
Expand All @@ -946,50 +972,92 @@ One of :SINK, :SOURCE, :BIDIRECTIONAL, or :UNDIRECTED."))
(setf (global-reg/32 hda +intctl+) 0))

(defun stream-go (hda stream-id)
;; run, IoC enabled, stream number (tag) 1.
(setf (sd-reg/32 hda stream-id +sdnctlsts+) #x00100006))
(setf (sd-reg/32 hda stream-id +sdnctlsts+)
(logior #x00100000 ; STRM = 1 (matches codec verb #x70610)
(ash 1 18) ; TP (traffic priority)
(ash 1 4) ; DEIE
(ash 1 3) ; FEIE
(ash 1 2) ; IOCE
(ash 1 1)))) ; RUN

(defun first-input-stream (hda)
(declare (ignore hda))
0)

(defun first-output-stream (hda)
(with-hda-access (hda)
(gcap-iss (global-reg/16 hda +gcap+))))
(gcap-oss (global-reg/16 hda +gcap+))))

(defun start-playback (hda buffer buffer-size codec dac pin &optional mixer)
(defun compute-period-bytes (hda stream-id)
(with-hda-access (hda)
(or (hda-stable-period-bytes hda)
(let* ((fifo-size (1+ (sd-reg/16 hda stream-id +sdnfifos+)))
(fifo-aligned (* (ceiling fifo-size 128) 128))
(period-bytes (max fifo-aligned *hda-min-period-bytes*)))
(mezzano.supervisor:debug-print-line
"HDA stream " stream-id " FIFO " fifo-size " period " period-bytes)
period-bytes))))

(defun start-playback (hda buffer buffer-size codec dac pin &optional mixer
&key
(period-bytes (compute-period-bytes hda (first-output-stream hda)))
(period-count +playback-period-count+)
(startup-delay-seconds 0)
(mute-for-startup nil))
(with-hda-access (hda)
(stream-reset hda (first-output-stream hda))
(write-bdl hda 0 buffer (truncate buffer-size 2))
(write-bdl hda 1 (+ buffer (truncate buffer-size 2)) (truncate buffer-size 2))
(prep-stream hda (first-output-stream hda) 0 1 buffer-size)
(when mixer
(command hda codec mixer #x3F07F)) ; unmute L/R out/in
(clear-pending-interrupt hda (first-output-stream hda))
(dotimes (i period-count)
(write-bdl hda i
(+ buffer (* i period-bytes))
period-bytes))
(prep-stream hda (first-output-stream hda) 0 period-count buffer-size)
(command hda codec dac #x70610) ; stream=1
(command hda codec dac #x24011) ; format
(command hda codec dac #x3b07f) ; unmute L/R out
(command hda codec pin #x3b07f) ; unmute L/R out
(command hda codec pin #x70740) ; enable output
(when mixer
(command hda codec mixer #x3F07F)) ; unmute L/R out/in
(if mute-for-startup
(progn
;; Mute the DAC output so the SRC can adapt silently while
;; the host audio backend initializes.
(command hda codec dac #x3b0ff)) ; mute
(progn
(command hda codec dac #x3b07f) ; unmute L/R out
(command hda codec pin #x3b07f) ; unmute L/R out
(command hda codec pin #x70740))) ; enable output
;; Enable global and stream interrupts.
(setf (global-reg/32 hda +intctl+) (logior #x80000000 (ash 1 (first-output-stream hda))))
(mezzano.supervisor:simple-irq-unmask (hda-irq hda))
(sys.int::dma-write-barrier)
(when (plusp startup-delay-seconds)
(sleep startup-delay-seconds))
(stream-go hda (first-output-stream hda))))

(defun clear-pending-interrupt (hda stream)
;; qemu is picky and requires a write to the 8-bit status part of
;; the register.
(setf (sd-reg/8 hda stream (+ +sdnctlsts+ 3)) (ash 1 2))) ; bcis
;; Clear any latched stream status in the status byte. qemu is picky and
;; requires a write to the 8-bit status part of the register.
(setf (sd-reg/8 hda stream (+ +sdnctlsts+ 3))
(logior (ash 1 4) ; dese
(ash 1 3) ; fifoe
(ash 1 2)))) ; bcis

(defun wait-for-buffer-interrupt (hda)
(let ((irq (hda-irq hda))
(stream (first-output-stream hda)))
(loop
(sync:wait-for-objects irq (pci:pci-device-boot-id (hda-pci-device hda)))
(multiple-value-bind (objects remaining)
(sync:wait-for-objects-with-timeout 0.02
irq
(pci:pci-device-boot-id (hda-pci-device hda)))
(declare (ignore remaining))
(when (null objects)
(return :timeout)))
(with-hda-access (hda)
(when (logbitp stream (global-reg/32 hda +intsts+))
(clear-pending-interrupt hda stream)
(mezzano.supervisor:simple-irq-unmask irq)
(return)))
(mezzano.supervisor:simple-irq-unmask irq))))
(return :interrupt)))
(mezzano.supervisor:simple-irq-unmask irq))))

;; Return a list of all pin widgets.
(defun pin-widgets (hda)
Expand Down Expand Up @@ -1041,74 +1109,158 @@ Returns NIL if there is no output path."
(t
(values (first direct-converters) nil)))))

(defun set-widget-power-state-d0 (node)
;; Set Power State = D0.
(command (hda node) (cad node) (nid node) #x70500))

(defun maybe-enable-eapd (widget)
(when (typep widget 'audio-pin-complex)
(let ((caps (parameter widget +parameter-pin-capabilities+)))
(when (not (zerop (parameter-pin-capabilities-eapd-capable caps)))
;; Enable EAPD/BTL.
(command (hda widget) (cad widget) (nid widget) #x70C02)))))

(defun log-selected-output-path (pin converter mixer)
(mezzano.supervisor:debug-print-line
"HDA selected pin " (nid pin)
" (" (pin-default-device pin) " " (pin-location pin) " " (pin-colour pin) ")"
" converter " (nid converter)
(if mixer
(format nil " mixer ~D" (nid mixer))
"")))

(defun hda-restart-stream (hda stream buffer period-bytes period-count buf-len)
"Reset the stream, rewrite BDL entries with the given period size,
prep the stream, and start DMA. Returns nothing."
(with-hda-access (hda)
(stream-reset hda stream)
(dotimes (i period-count)
(write-bdl hda i (+ buffer (* i period-bytes)) period-bytes))
(prep-stream hda stream 0 period-count buf-len)
(setf (global-reg/32 hda +intctl+)
(logior #x80000000 (ash 1 stream)))
(clear-pending-interrupt hda stream)
(mezzano.supervisor:simple-irq-unmask (hda-irq hda))
(sys.int::dma-write-barrier)
(stream-go hda stream)))

;; TODO: This should stream to anything that looks vaugely output-like, instead
;; of a single pin.
(defmethod mezzano.driver.sound:sound-card-run ((hda hda) buffer-fill-callback)
(handler-case
(let* ((buffer (hda-dma-buffer-phys hda))
(buf-len #x2000);(hda-dma-buffer-size hda))
(half-buf-len (truncate buf-len 2))
(n-samples (truncate half-buf-len 2)) ; buf-len is in bytes (2 per sample) and we want to fill only half the buffer at once
(float-sample-buffer (make-array n-samples :element-type 'single-float))
(output-pin (default-output-pin hda))
(output-stream (first-output-stream hda))
(buffer-offset 0)
(stop-countdown nil))
(labels ((store-sample (sample offset)
;; Clamp to limits, don't wrap.
(let* ((sample-clamped (max (min sample 1.0f0) -1.0f0))
(sample-rescaled (if (< sample-clamped 0.0f0)
(* sample-clamped 32768.0f0)
(* sample-clamped 32767.0f0)))
(sample-16bit (truncate sample-rescaled)))
(declare (optimize speed (safety 0))
(type single-float sample-clamped sample-rescaled)
(type fixnum sample-16bit))
(setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset)) (ldb (byte 8 0) sample-16bit)
(mezzano.supervisor::physical-memref-unsigned-byte-8 buffer (+ buffer-offset offset offset 1)) (ldb (byte 8 8) sample-16bit))))
(refill-fifo ()
(with-hda-access (hda)
(locally
(declare (optimize speed (safety 0))
(type (simple-array single-float (*)) float-sample-buffer)
(type fixnum n-samples))
(dotimes (i n-samples)
(store-sample (aref float-sample-buffer i) i))))
(cond ((eql buffer-offset 0)
(setf buffer-offset half-buf-len))
(t
(setf buffer-offset 0)))
(cond ((funcall buffer-fill-callback float-sample-buffer 0 n-samples)
(setf stop-countdown nil))
((not stop-countdown)
(setf stop-countdown 4)))))
;; Prepopulate the initial buffer.
(funcall buffer-fill-callback float-sample-buffer 0 n-samples)
(stream (first-output-stream hda))
(pb (compute-period-bytes hda stream))
(pc +playback-period-count+)
(bl (* pb pc))
(n-samples (truncate pb 2))
(buf (make-array n-samples :element-type 'single-float))
(pin (default-output-pin hda))
(woff 0)
(stop-ct nil)
(fifo 0)
(muted (not *i-want-garbage*))
(startup (if *i-want-garbage* 0 *hda-startup-periods*))
(startup-sec (if *i-want-garbage* 0.0
(/ (* pb *hda-startup-periods*) 176400.0)))
(udac nil) (upin nil) (ucad nil)
(ucd 0)
(ucnt 0))
(setf (hda-period-bytes hda) pb)
(unless pin
(error "No HDA output pin with a playback path found."))
(labels ((flush ()
(dotimes (i n-samples)
(let* ((s (aref buf i))
(sc (max (min s 1.0f0) -1.0f0))
(sr (if (< sc 0.0f0) (* sc 32768.0) (* sc 32767.0)))
(si (truncate sr)))
(setf (mezzano.supervisor::physical-memref-unsigned-byte-8
buffer (+ woff i i)) (ldb (byte 8 0) si)
(mezzano.supervisor::physical-memref-unsigned-byte-8
buffer (+ woff i i 1)) (ldb (byte 8 8) si))))
(sys.int::dma-write-barrier)
(setf woff (rem (+ woff pb) bl)))
(refill ()
(cond ((plusp startup) (fill buf 0.0))
((funcall buffer-fill-callback buf 0 n-samples)
(setf stop-ct nil))
((not stop-ct)
(setf stop-ct *hda-idle-periods*)))
(flush))
(fill-silence ()
(let ((save woff))
(setf woff 0)
(dotimes (i pc) (fill buf 0.0) (flush))
(setf woff save)))
(try-unmute ()
(when (plusp startup)
(decf startup)
(when (zerop startup)
(mezzano.supervisor:debug-print-line
"HDA startup complete, unmuting")
(with-hda-access (hda)
(command hda ucad udac #x3b07f)
(command hda ucad upin #x3b07f)
(command hda ucad upin #x70740))))))
;; Init.
(dotimes (i bl) (setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0))
(with-hda-access (hda)
;; Clear the whole buffer.
(dotimes (i buf-len)
(setf (mezzano.supervisor::physical-memref-unsigned-byte-8 buffer i) 0))
;; Unmask the interrupt, flush any pending IRQs.
(clear-pending-interrupt hda output-stream)
(mezzano.supervisor:simple-irq-unmask (hda-irq hda)))
;; Begin playback.
(multiple-value-bind (converter mixer)
(output-path output-pin)
(start-playback hda buffer buf-len (cad output-pin) (nid converter) (nid output-pin) (and mixer (nid mixer))))
(clear-pending-interrupt hda stream)
(mezzano.supervisor:simple-irq-unmask (hda-irq hda))
(setf fifo (1+ (sd-reg/16 hda stream +sdnfifos+))))
(unless muted (dotimes (i pc) (refill)))
;; Output path + start.
(multiple-value-bind (c m) (output-path pin)
(unless c (error "HDA output pin ~D has no converter." (nid pin)))
(log-selected-output-path pin c m)
(set-widget-power-state-d0 pin)
(set-widget-power-state-d0 c)
(when m (set-widget-power-state-d0 m))
(maybe-enable-eapd pin)
(when muted
(setf udac (nid c) upin (nid pin) ucad (cad pin)))
(start-playback hda buffer bl (cad pin) (nid c) (nid pin)
(and m (nid m))
:period-bytes pb :period-count pc
:startup-delay-seconds startup-sec
:mute-for-startup muted))
(mezzano.supervisor:debug-print-line
"HDA entering loop, lpib=" (sd-reg/32 hda stream +sdnlpib+))
;; Loop.
(unwind-protect
(loop
;; Wait for the dma position to move from the
;; current buffer to the other buffer.
(let* ((dmap (dma-position hda output-stream))
(current-offset (truncate dmap half-buf-len)))
(when (not (eql current-offset (truncate buffer-offset half-buf-len)))
(when stop-countdown
(when (zerop stop-countdown)
(return))
(decf stop-countdown))
;; Refill buffer.
(refill-fifo)))
(wait-for-buffer-interrupt hda))
(ecase (wait-for-buffer-interrupt hda)
(:timeout)
(:interrupt
(let* ((lp (sd-reg/32 hda stream +sdnlpib+))
(lpf (mod (+ lp fifo) bl))
(co (truncate lpf pb)))
(unless (eql co (truncate woff pb))
(when stop-ct
(when (zerop stop-ct) (return))
(decf stop-ct))
(refill)
;; If the hardware crossed into the next period
;; while we were refilling, we're falling behind.
(let* ((lp2 (sd-reg/32 hda stream +sdnlpib+))
(lpf2 (mod (+ lp2 fifo) bl))
(co2 (truncate lpf2 pb)))
(when (not (eql co2 co))
(incf ucnt)))
(try-unmute)))))
(when (plusp ucd) (decf ucd))
(when (and (zerop ucd) (> ucnt 3))
(let ((npb (* (ceiling (+ pb 128) 128) 128)))
(mezzano.supervisor:debug-print-line
"HDA underrun, period " pb " → " npb)
(setf (hda-stable-period-bytes hda) npb
pb npb (hda-period-bytes hda) npb
bl (* npb pc) n-samples (truncate npb 2)
buf (make-array n-samples :element-type 'single-float)
woff 0 ucd (* 8 pc) ucnt 0)
(hda-restart-stream hda stream buffer pb pc bl)
(fill-silence))))
(with-hda-access (hda)
(stream-reset hda (first-output-stream hda))
(mezzano.supervisor:simple-irq-mask (hda-irq hda))))))
Expand All @@ -1121,5 +1273,6 @@ Returns NIL if there is no output path."

(pci:define-pci-driver intel-hda intel-hda-probe
((#x8086 #x2668) ; ICH6 HDA
(#x8086 #x27D8)) ; ICH7 HDA
(#x8086 #x27D8) ; ICH7 HDA
(#x8086 #x293E)); ICH9 HDA
())
Loading