diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c7fe7d..3b4ac5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: if: matrix.platform == 'ubuntu-latest' run: | sudo apt-get update - sudo apt-get install -qq pkg-config gcc-arm-linux-gnueabihf + sudo apt-get install -qq pkg-config gcc-arm-linux-gnueabihf libftdi1-dev - name: Build-Linux-32b-arm if: matrix.platform == 'ubuntu-latest' diff --git a/go.mod b/go.mod index 3c7d912..efd9632 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/go-sql-driver/mysql v1.7.0 github.com/peterh/liner v1.2.2 github.com/sbinet/pmon v0.5.0 + github.com/ziutek/ftdi v0.0.1 go-hep.org/x/hep v0.32.1 golang.org/x/sync v0.1.0 golang.org/x/sys v0.7.0 diff --git a/go.sum b/go.sum index 724a6db..0eab969 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/ziutek/ftdi v0.0.1 h1:IrFtqLisIzEfgSnuLg1suZYLkn227DTyE464XswAGK4= +github.com/ziutek/ftdi v0.0.1/go.mod h1:fUzQhjslJJSlwoMW5Cd+0RUc8KuMDfsuZB+wfJAz1eg= +github.com/ziutek/lcd v0.0.0-20141212131202-924f223d0903/go.mod h1:ZBCPhfHIcCtzsrXIcyEiSPDKHrjT9fXtJNKj2t1HCKw= go-hep.org/x/hep v0.32.1 h1:O96fOyMP+4ET8X+Uu38VFdegQb7rL0rjmFqXMCSm4VM= go-hep.org/x/hep v0.32.1/go.mod h1:VX3IVUv0Ku5bgWhE+LxRQ1aT7BmWWxSxQu02hfsoeRI= go.nanomsg.org/mangos/v3 v3.2.1/go.mod h1:RxVwsn46YtfJ74mF8MeVo+MFjg545KCI50NuZrFXmzc= diff --git a/rpi/const.go b/rpi/const.go new file mode 100644 index 0000000..b3aac10 --- /dev/null +++ b/rpi/const.go @@ -0,0 +1,18 @@ +// Copyright 2020 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpi + +const ( + MaxEventSize = (hardrocV2SLCFrameSize+1)*MaxNumASICs + (20*ASICMemDepth+2)*MaxNumASICs + 3 + MaxFwHeaderSize + 2 + MaxAnalogDataSize + 50 + + MaxAnalogDataSize = 1024*64*2 + 20 + MaxFwHeaderSize = 50 + MaxNumASICs = 48 // max number of hardrocs per dif that the system can handle + MaxNumDIFs = 200 // max number of difs that the system can handle + ASICMemDepth = 128 // memory depth of one asic . 128 is for hardroc v1 + + hardrocV2SLCFrameSize = 109 + microrocSLCFrameSize = 74 +) diff --git a/rpi/device.go b/rpi/device.go new file mode 100644 index 0000000..053ebf7 --- /dev/null +++ b/rpi/device.go @@ -0,0 +1,257 @@ +// Copyright 2020 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpi + +import ( + "encoding/binary" + "fmt" + "io" + + "github.com/ziutek/ftdi" +) + +type ftdiDevice interface { + Reset() error + + SetBitmode(iomask byte, mode ftdi.Mode) error + SetFlowControl(flowctrl ftdi.FlowCtrl) error + SetLatencyTimer(lt int) error + SetWriteChunkSize(cs int) error + SetReadChunkSize(cs int) error + PurgeBuffers() error + + io.Writer + io.Reader + io.Closer +} + +type device struct { + vid uint16 // vendor ID + pid uint16 // product ID + ft ftdiDevice // handle to the FTDI device +} + +var ( + ftdiOpen = ftdiOpenImpl +) + +func ftdiOpenImpl(vid, pid uint16) (ftdiDevice, error) { + dev, err := ftdi.OpenFirst(int(vid), int(pid), ftdi.ChannelAny) + return dev, err +} + +func newDevice(vid, pid uint16) (*device, error) { + ft, err := ftdiOpen(vid, pid) + if err != nil { + return nil, fmt.Errorf("could not open FTDI device (vid=0x%x, pid=0x%x): %w", vid, pid, err) + } + + dev := &device{vid: vid, pid: pid, ft: ft} + err = dev.init() + if err != nil { + ft.Close() + return nil, fmt.Errorf("could not initialize FTDI device (vid=0x%x, pid=0x%x): %w", vid, pid, err) + } + + return dev, nil +} + +func (dev *device) init() error { + var err error + + err = dev.ft.Reset() + if err != nil { + return fmt.Errorf("could not reset USB: %w", err) + } + + err = dev.ft.SetBitmode(0, ftdi.ModeBitbang) + if err != nil { + return fmt.Errorf("could not disable bitbang: %w", err) + } + + err = dev.ft.SetFlowControl(ftdi.FlowCtrlDisable) + if err != nil { + return fmt.Errorf("could not disable flow control: %w", err) + } + + err = dev.ft.SetLatencyTimer(2) + if err != nil { + return fmt.Errorf("could not set latency timer to 2: %w", err) + } + + err = dev.ft.SetWriteChunkSize(0xffff) + if err != nil { + return fmt.Errorf("could not set write chunk-size to 0xffff: %w", err) + } + + err = dev.ft.SetReadChunkSize(0xffff) + if err != nil { + return fmt.Errorf("could not set read chunk-size to 0xffff: %w", err) + } + + if dev.pid == 0x6014 { + err = dev.ft.SetBitmode(0, ftdi.ModeReset) + if err != nil { + return fmt.Errorf("could not reset bit mode: %w", err) + } + } + + err = dev.ft.PurgeBuffers() + if err != nil { + return fmt.Errorf("could not purge USB buffers: %w", err) + } + + return err +} + +func (dev *device) close() error { + return dev.ft.Close() +} + +func (dev *device) usbRegRead(addr uint32) (uint32, error) { + a := (addr | 0x4000) & 0x7fff + p := []byte{uint8(a>>8) & 0xff, uint8(a>>0) & 0xff, 0, 0} + + n, err := dev.ft.Write(p[:2]) + switch { + case err != nil: + return 0, fmt.Errorf("could not write USB addr 0x%x: %w", addr, err) + case n != len(p[:2]): + return 0, fmt.Errorf("could not write USB addr 0x%x: %w", addr, io.ErrShortWrite) + } + + _, err = io.ReadFull(dev.ft, p) + if err != nil { + return 0, fmt.Errorf("could not read register 0x%x: %w", addr, err) + } + + v := binary.BigEndian.Uint32(p) + return v, nil +} + +func (dev *device) usbCmdWrite(cmd uint32) error { + addr := cmd | 0x8000 // keep only 14 LSB, write, so bit 14=0,register mode, so bit 15=0 + buf := []byte{uint8(addr>>8) & 0xff, uint8(addr>>0) & 0xff} + + n, err := dev.ft.Write(buf) + switch { + case err != nil: + return fmt.Errorf("could not write USB command 0x%x: %w", cmd, err) + case n != len(buf): + return fmt.Errorf("could not write USB command 0x%x: %w", cmd, io.ErrShortWrite) + } + + return nil +} + +func (dev *device) usbRegWrite(addr, v uint32) error { + var ( + a = addr & 0x3fff + p = make([]byte, 6) + ) + + binary.BigEndian.PutUint16(p[:2], uint16(a)) + binary.BigEndian.PutUint32(p[2:], uint32(v)) + and0xff(p) + + n, err := dev.ft.Write(p) + switch { + case err != nil: + return fmt.Errorf("could not write USB register (0x%x, 0x%x): %w", addr, v, err) + case n != len(p): + return fmt.Errorf("could not write USB register (0x%x, 0x%x): %w", addr, v, io.ErrShortWrite) + } + return nil +} + +func (dev *device) setChipTypeRegister(v uint32) error { + return dev.usbRegWrite(0x00, v) +} + +func (dev *device) setDIFID(v uint32) error { + return dev.usbRegWrite(0x01, v) +} + +func (dev *device) setControlRegister(v uint32) error { + return dev.usbRegWrite(0x03, v) +} + +func (dev *device) getControlRegister() (uint32, error) { + return dev.usbRegRead(0x03) +} + +func (dev *device) difCptReset() error { + const addr = 0x03 + v, err := dev.usbRegRead(addr) + if err != nil { + return fmt.Errorf("could not read register 0x%x", addr) + } + + v |= 0x2000 + err = dev.usbRegWrite(addr, v) + if err != nil { + return fmt.Errorf("could not write to register 0x%x", addr) + } + + v &= 0xffffdfff + err = dev.usbRegWrite(addr, v) + if err != nil { + return fmt.Errorf("could not write to register 0x%x", addr) + } + + return nil +} + +func (dev *device) setPwr2PwrARegister(v uint32) error { return dev.usbRegWrite(0x40, v) } +func (dev *device) setPwrA2PwrDRegister(v uint32) error { return dev.usbRegWrite(0x41, v) } +func (dev *device) setPwrD2DAQRegister(v uint32) error { return dev.usbRegWrite(0x42, v) } +func (dev *device) setDAQ2PwrDRegister(v uint32) error { return dev.usbRegWrite(0x43, v) } +func (dev *device) setPwrD2PwrARegister(v uint32) error { return dev.usbRegWrite(0x44, v) } + +func (dev *device) setEventsBetweenTemperatureReadout(v uint32) error { + return dev.usbRegWrite(0x55, v) +} + +func (dev *device) setAnalogConfigureRegister(v uint32) error { + return dev.usbRegWrite(0x60, v) +} + +func (dev *device) usbFwVersion() (uint32, error) { + return dev.usbRegRead(0x100) +} + +func (dev *device) hardrocFlushDigitalFIFO() error { + return nil +} + +func (dev *device) hardrocStopDigitalAcquisitionCommand() error { + return dev.usbCmdWrite(0x02) +} + +func (dev *device) hardrocSLCStatusRead() (uint32, error) { + return dev.usbRegRead(0x06) +} + +func (dev *device) hardrocCmdSLCWrite() error { + return dev.usbCmdWrite(0x01) +} + +func (dev *device) hardrocCmdSLCWriteCRC(v uint16) error { + p := make([]byte, 2) + binary.BigEndian.PutUint16(p, v) + _, err := dev.ft.Write(p) + return err +} + +func (dev *device) cmdSLCWriteSingleSLCFrame(p []byte) error { + _, err := dev.ft.Write(p) + return err +} + +func and0xff(p []byte) { + for i := range p { + p[i] &= 0xff + } +} diff --git a/rpi/device_test.go b/rpi/device_test.go new file mode 100644 index 0000000..1c142c3 --- /dev/null +++ b/rpi/device_test.go @@ -0,0 +1,144 @@ +// Copyright 2020 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpi + +import ( + "fmt" + "io" + "testing" +) + +func TestFTDIOpen(t *testing.T) { + dev, err := ftdiOpenImpl(0, 0) + if err == nil { + _ = dev.Close() + } +} + +type ierr struct { + n int + e error +} + +type failingRW struct { + rs []ierr + ws []ierr +} + +func (rw *failingRW) Read(p []byte) (int, error) { + i := len(rw.rs) + rs := rw.rs[i-1] + rw.rs = rw.rs[:i-1] + return rs.n, rs.e +} + +func (rw *failingRW) Write(p []byte) (int, error) { + i := len(rw.ws) + ws := rw.ws[i-1] + rw.ws = rw.ws[:i-1] + return ws.n, ws.e +} + +func TestDevice(t *testing.T) { + ftdiOpen = ftdiOpenTest + defer func() { + ftdiOpen = ftdiOpenImpl + }() + + dev, err := newDevice(0x1, 0x2) + if err != nil { + t.Fatalf("could not create fake device: %+v", err) + } + defer dev.close() + + var ( + rw failingRW + ft = fakeDevice{buf: &rw} + ) + dev.ft = &ft + + for _, tc := range []struct { + name string + f func() error + want error + }{ + { + name: "usbRegRead-eof", + f: func() error { + rw.ws = append(rw.ws, ierr{0, io.EOF}) + _, err := dev.usbRegRead(0x1234) + return err + }, + want: fmt.Errorf("could not write USB addr 0x%x: %w", 0x1234, io.EOF), + }, + { + name: "usbRegRead-short-write", + f: func() error { + rw.ws = append(rw.ws, ierr{1, nil}) + _, err := dev.usbRegRead(0x1234) + return err + }, + want: fmt.Errorf("could not write USB addr 0x%x: %w", 0x1234, io.ErrShortWrite), + }, + { + name: "usbRegRead-err-read", + f: func() error { + rw.ws = append(rw.ws, ierr{2, nil}) + rw.rs = append(rw.rs, ierr{2, io.ErrUnexpectedEOF}) + _, err := dev.usbRegRead(0x1234) + return err + }, + want: fmt.Errorf("could not read register 0x%x: %w", 0x1234, io.ErrUnexpectedEOF), + }, + { + name: "usbCmdWrite-eof", + f: func() error { + rw.ws = append(rw.ws, ierr{0, io.EOF}) + return dev.usbCmdWrite(0x1234) + }, + want: fmt.Errorf("could not write USB command 0x%x: %w", 0x1234, io.EOF), + }, + { + name: "usbCmdWrite-short-write", + f: func() error { + rw.ws = append(rw.ws, ierr{1, nil}) + return dev.usbCmdWrite(0x1234) + }, + want: fmt.Errorf("could not write USB command 0x%x: %w", 0x1234, io.ErrShortWrite), + }, + { + name: "usbRegWrite-eof", + f: func() error { + rw.ws = append(rw.ws, ierr{0, io.EOF}) + return dev.usbRegWrite(0x1234, 0x255) + }, + want: fmt.Errorf("could not write USB register (0x%x, 0x%x): %w", 0x1234, 0x255, io.EOF), + }, + { + name: "usbRegWrite-short-write", + f: func() error { + rw.ws = append(rw.ws, ierr{1, nil}) + return dev.usbRegWrite(0x1234, 0x255) + }, + want: fmt.Errorf("could not write USB register (0x%x, 0x%x): %w", 0x1234, 0x255, io.ErrShortWrite), + }, + } { + t.Run(tc.name, func(t *testing.T) { + got := tc.f() + switch { + case got == nil && tc.want == nil: + // ok + case got == nil && tc.want != nil: + t.Fatalf("got=%v, want=%v", got, tc.want) + case got != nil && tc.want != nil: + if got, want := got.Error(), tc.want.Error(); got != want { + t.Fatalf("got= %v\nwant=%v", got, want) + } + case got != nil && tc.want == nil: + t.Fatalf("got=%+v\nwant=%v", got, tc.want) + } + }) + } +} diff --git a/rpi/readout.go b/rpi/readout.go new file mode 100644 index 0000000..7a52541 --- /dev/null +++ b/rpi/readout.go @@ -0,0 +1,343 @@ +// Copyright 2021 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpi + +import ( + "fmt" + "io" + "time" + + "github.com/go-daq/tdaq/log" + "github.com/go-lpc/mim/internal/crc16" + "github.com/go-lpc/mim/internal/eformat" +) + +type asicKind uint32 + +const ( + microrocASIC asicKind = 11 + hardrocASIC asicKind = 2 +) + +// Readout reads data out of a digital interface board (DIF). +type Readout struct { + msg log.MsgStream + dev *device + name string + difID uint32 + asic asicKind // asic type + nasics int // number of asics + ctlreg uint32 // control register + curSC uint32 // current slow-control status + reg struct { + p2pa uint32 // power to power A + pa2pd uint32 // power A to power D + pd2daq uint32 // power D to DAQ + daq2pd uint32 // DAQ to power D + pd2pa uint32 // power D to power A + } + // temp [2]float32 // temperatures +} + +// NewReadout creates a new DIF readout. +func NewReadout(name string, prodID uint32, msg log.MsgStream) (*Readout, error) { + dev, err := newDevice(0x0403, uint16(prodID)) + if err != nil { + return nil, fmt.Errorf("could not find DIF driver (%s, 0x%x): %w", name, prodID, err) + } + + rdo := &Readout{ + msg: msg, + dev: dev, + name: name, + asic: hardrocASIC, + nasics: MaxNumASICs, + ctlreg: 0x80181b00, // ILC CCC + } + rdo.reg.p2pa = 0x3e8 + rdo.reg.pa2pd = 0x3e6 + rdo.reg.pd2daq = 0x4e + rdo.reg.daq2pd = 0x4e + rdo.reg.pd2pa = 0x4e + _, err = fmt.Sscanf(name, "FT101%03d", &rdo.difID) + if err != nil { + _ = dev.close() + return nil, fmt.Errorf("could not find DIF-id from %q: %w", name, err) + } + + return rdo, nil +} + +func (rdo *Readout) close() error { + err := rdo.dev.close() + if err != nil { + return fmt.Errorf("could not close DIF driver: %w", err) + } + return nil +} + +func (rdo *Readout) start() error { + return rdo.dev.hardrocFlushDigitalFIFO() +} + +func (rdo *Readout) stop() error { + var err error + + err = rdo.dev.hardrocFlushDigitalFIFO() + if err != nil { + return fmt.Errorf("could not flush digital FIFO: %w", err) + } + + err = rdo.dev.hardrocStopDigitalAcquisitionCommand() + if err != nil { + return fmt.Errorf("could not stop digital acquisition: %w", err) + } + + err = rdo.dev.hardrocFlushDigitalFIFO() + if err != nil { + return fmt.Errorf("could not flush digital FIFO: %w", err) + } + + return nil +} + +func (rdo *Readout) configureRegisters() error { + var err error + err = rdo.dev.setDIFID(rdo.difID) + if err != nil { + return fmt.Errorf("could not set DIF ID to 0x%x: %w", rdo.difID, err) + } + + err = rdo.doRefreshNumASICs() + if err != nil { + return fmt.Errorf("could not refresh #ASICs: %w", err) + } + + err = rdo.dev.setEventsBetweenTemperatureReadout(5) + if err != nil { + return fmt.Errorf("could not set #events Temp readout: %w", err) + } + + err = rdo.dev.setAnalogConfigureRegister(0xc0054000) + if err != nil { + return fmt.Errorf("could not configure analog register: %w", err) + } + + err = rdo.dev.hardrocFlushDigitalFIFO() + if err != nil { + return fmt.Errorf("could not flush digital FIFO: %w", err) + } + + fw, err := rdo.dev.usbFwVersion() + if err != nil { + return fmt.Errorf("could not get firmware version: %w", err) + } + rdo.msg.Infof("dif %s fw: 0x%x", rdo.name, fw) + + err = rdo.dev.difCptReset() + if err != nil { + return fmt.Errorf("could not reset DIF cpt: %w", err) + } + + err = rdo.dev.setChipTypeRegister(map[asicKind]uint32{ + hardrocASIC: 0x100, + microrocASIC: 0x1000, + }[rdo.asic]) + if err != nil { + return fmt.Errorf("could not set chip type: %w", err) + } + + err = rdo.dev.setControlRegister(rdo.ctlreg) + if err != nil { + return fmt.Errorf("could not set control register: %w", err) + } + + ctlreg, err := rdo.dev.getControlRegister() + if err != nil { + return fmt.Errorf("could not get control register: %w", err) + } + rdo.msg.Infof("ctl reg: 0x%x", ctlreg) + + err = rdo.dev.setPwr2PwrARegister(rdo.reg.p2pa) + if err != nil { + return fmt.Errorf("could not set pwr to A register: %w", err) + } + + err = rdo.dev.setPwrA2PwrDRegister(rdo.reg.pa2pd) + if err != nil { + return fmt.Errorf("could not set A to D register: %w", err) + } + + err = rdo.dev.setPwrD2DAQRegister(rdo.reg.pd2daq) + if err != nil { + return fmt.Errorf("could not set D to DAQ register: %w", err) + } + + err = rdo.dev.setDAQ2PwrDRegister(rdo.reg.daq2pd) + if err != nil { + return fmt.Errorf("could not set DAQ to D register: %w", err) + } + + err = rdo.dev.setPwrD2PwrARegister(rdo.reg.pd2pa) + if err != nil { + return fmt.Errorf("could not set D to A register: %w", err) + } + + return err +} + +func (rdo *Readout) configureChips(scFrame [][]byte) (uint32, error) { + var frame []byte + switch rdo.asic { + case hardrocASIC: + frame = make([]byte, hardrocV2SLCFrameSize) + case microrocASIC: + frame = make([]byte, microrocSLCFrameSize) + default: + return 0, fmt.Errorf("unknown ASIC kind %v", rdo.asic) + } + + crc := crc16.New(nil) + err := rdo.dev.hardrocCmdSLCWrite() + if err != nil { + return 0, fmt.Errorf("%s could not send start SLC command to DIF: %w", + rdo.name, err, + ) + } + + for i := rdo.nasics; i > 0; i-- { + copy(frame, scFrame[i-1]) + _, err = crc.Write(frame) + if err != nil { + return 0, fmt.Errorf("%s could not update CRC-16: %w", rdo.name, err) + } + + err = rdo.dev.cmdSLCWriteSingleSLCFrame(frame) + if err != nil { + return 0, fmt.Errorf("%s could not send SLC frame to DIF: %w", + rdo.name, err, + ) + } + } + + crc16 := crc.Sum16() + err = rdo.dev.hardrocCmdSLCWriteCRC(crc16) + if err != nil { + return 0, fmt.Errorf("%s could not send CRC 0x%x to SLC: %w", + rdo.name, crc16, err, + ) + } + + time.Sleep(400 * time.Millisecond) // was 500ms + + st, err := rdo.doReadSLCStatus() + if err != nil { + return 0, fmt.Errorf("%s could not read SLC status: %w", + rdo.name, err, + ) + } + rdo.curSC = st + + return st, nil +} + +func (rdo *Readout) Readout(p []byte) (int, error) { + var ( + dif eformat.DIF + w = bwriter{p: p} + dec = eformat.NewDecoder(uint8(rdo.difID), io.TeeReader(rdo.dev.ft, &w)) + ) + err := dec.Decode(&dif) + if err != nil { + return w.c, fmt.Errorf("%s could not decode DIF data: %w", + rdo.name, err, + ) + } + + return w.c, nil +} + +func (rdo *Readout) doRefreshNumASICs() error { + var ( + v uint32 + l1 = uint8(rdo.nasics>>0) & 0xff + l2 = uint8(rdo.nasics>>8) & 0xff + l3 = uint8(rdo.nasics>>16) & 0xff + l4 = uint8(rdo.nasics>>24) & 0xff + n = l1 + l2 + l3 + l4 + ) + + f := func(n, l1, l2, l3, l4 uint8) uint32 { + return uint32(n) + uint32(l1)<<8 + uint32(l2)<<14 + uint32(l3)<<20 + uint32(l4)<<26 + } + switch rdo.asic { + case microrocASIC: + v = f(n, l1, l2, l3, l4) + default: + v = f(n, n, 0, 0, 0) + } + + err := rdo.dev.usbRegWrite(0x05, v) + if err != nil { + return fmt.Errorf("could not refresh num-asics: %w", err) + } + + return nil +} + +func (rdo *Readout) doReadSLCStatus() (uint32, error) { + st, err := rdo.dev.hardrocSLCStatusRead() + if err != nil { + return 0, fmt.Errorf("could not read SLC status: %w", err) + } + rdo.curSC = st + return st, nil +} + +func (rdo *Readout) checkRW(start, count uint32) error { + for ireg := start; ireg < start+count; ireg++ { + err := rdo.dev.usbRegWrite(2, ireg) + if err != nil { + return fmt.Errorf("could not write to 0x%x: %w", ireg, err) + } + if ireg != start+count-1 { + continue + } + + regctl, err := rdo.dev.usbRegRead(2) + if err != nil { + return fmt.Errorf("could not read register from FT245: %w", err) + } + if regctl != ireg { + return fmt.Errorf("invalid register value from FT245: got=0x%x, want=0x%x", + regctl, ireg, + ) + } + } + + return nil +} + +func (rdo *Readout) setPowerManagment(p2pa, pa2pd, pd2daq, daq2pd, pd2pa uint32) { + rdo.reg.p2pa = p2pa + rdo.reg.pa2pd = pa2pd + rdo.reg.pd2daq = pd2daq + rdo.reg.daq2pd = daq2pd + rdo.reg.pd2pa = pd2pa +} + +type bwriter struct { + p []byte + c int +} + +func (w *bwriter) Write(p []byte) (int, error) { + if w.c >= len(w.p) { + return 0, io.EOF + } + n := copy(w.p[w.c:], p) + w.c += n + return n, nil +} diff --git a/rpi/readout_test.go b/rpi/readout_test.go new file mode 100644 index 0000000..f6f59fb --- /dev/null +++ b/rpi/readout_test.go @@ -0,0 +1,215 @@ +// Copyright 2020 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpi + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strings" + "testing" + + "github.com/go-daq/tdaq/log" + "github.com/go-lpc/mim/internal/eformat" + "github.com/ziutek/ftdi" +) + +func ftdiOpenTest(vid, pid uint16) (ftdiDevice, error) { + return &fakeDevice{buf: new(bytes.Buffer)}, nil +} + +func TestReadout(t *testing.T) { + ftdiOpen = ftdiOpenTest + defer func() { + ftdiOpen = ftdiOpenImpl + }() + + for _, tc := range []struct { + name string + err error + id uint32 + }{ + { + name: "FT101xxx", + err: fmt.Errorf("could not find DIF-id from %q: %s", "FT101xxx", errors.New("expected integer")), + }, + { + name: "FT101", + err: fmt.Errorf("could not find DIF-id from %q: %s", "FT101", io.EOF), + }, + { + name: "FT10142", + id: 42, + }, + { + name: "FT101042", + id: 42, + }, + } { + t.Run(tc.name, func(t *testing.T) { + rdo, err := NewReadout(tc.name, 0x6014, nil) + if err == nil && tc.err != nil { + rdo.close() + t.Fatalf("expected an error") + } + switch { + case tc.err != nil: + if got, want := err.Error(), tc.err.Error(); got != want { + t.Fatalf("invalid error:\ngot= %v\nwant=%v", got, want) + } + default: + defer rdo.close() + if rdo.difID != tc.id { + t.Fatalf("invalid DIF-id: got=%d, want=%d", rdo.difID, tc.id) + } + } + }) + } + + const ( + name = "FT101042" + prodID = 0x6014 + ) + + rdo, err := NewReadout(name, prodID, log.NewMsgStream("readout-"+name, log.LvlDebug, os.Stderr)) + if err != nil { + t.Fatalf("could not create readout: %+v", err) + } + if got, want := rdo.difID, uint32(42); got != want { + t.Fatalf("invalid DIF-ID: got=%d, want=%d", got, want) + } + + err = rdo.configureRegisters() + if err != nil { + t.Fatalf("could not configure registers: %+v", err) + } + + slow := make([][]byte, rdo.nasics) + for i := range slow { + slow[i] = make([]byte, hardrocV2SLCFrameSize) + } + _, err = rdo.configureChips(slow) + if err != nil { + t.Fatalf("could not configure chips: %+v", err) + } + + err = rdo.start() + if err != nil { + t.Fatalf("could not start readout: %+v", err) + } + + data := make([]byte, MaxEventSize) + { + w := new(bytes.Buffer) + dif := eformat.DIF{ + Header: eformat.GlobalHeader{ + ID: uint8(rdo.difID), + DTC: 10, + ATC: 11, + GTC: 12, + AbsBCID: 0x0000112233445566, + TimeDIFTC: 0x00112233, + }, + Frames: []eformat.Frame{ + { + Header: 1, + BCID: 0x001a1b1c, + Data: [16]uint8{0xa, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + { + Header: 2, + BCID: 0x002a2b2c, + Data: [16]uint8{ + 0xb, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 210, 211, 212, 213, 214, 215, + }, + }, + }, + } + err = eformat.NewEncoder(w).Encode(&dif) + if err != nil { + t.Fatalf("could not encode DIF data: %+v", err) + } + rdo.dev.ft = &fakeDevice{w} + } + n, err := rdo.Readout(data) + if err != nil { + t.Fatalf("could not readout data: %+v", err) + } + if n <= 0 { + t.Fatalf("could not readout data: n=%d", n) + } + data = data[:n] + _ = data + + err = rdo.stop() + if err != nil { + t.Fatalf("could not stop readout: %+v", err) + } + + err = rdo.close() + if err != nil { + t.Fatalf("could not close readout: %+v", err) + } +} + +func TestInvalidReadout(t *testing.T) { + want := fmt.Errorf("no such device") + ftdiOpen = func(vid, pid uint16) (ftdiDevice, error) { return nil, want } + defer func() { + ftdiOpen = ftdiOpenImpl + }() + + rdo, err := NewReadout("FT101042", 0x6014, nil) + if err == nil { + _ = rdo.close() + t.Fatalf("expected an error, got=%v", err) + } + if got, want := err.Error(), want.Error(); !strings.Contains(got, want) { + t.Fatalf("invalid error.\ngot= %v\nwant=%v\n", got, want) + } +} + +type fakeDevice struct { + buf io.ReadWriter +} + +func (dev *fakeDevice) Reset() error { return nil } + +func (dev *fakeDevice) SetBitmode(iomask byte, mode ftdi.Mode) error { + return nil +} + +func (dev *fakeDevice) SetFlowControl(flowctrl ftdi.FlowCtrl) error { + return nil +} + +func (dev *fakeDevice) SetLatencyTimer(lt int) error { + return nil +} + +func (dev *fakeDevice) SetWriteChunkSize(cs int) error { + return nil +} + +func (dev *fakeDevice) SetReadChunkSize(cs int) error { + return nil +} + +func (dev *fakeDevice) PurgeBuffers() error { + return nil +} + +func (dev *fakeDevice) Read(p []byte) (int, error) { + return dev.buf.Read(p) +} + +func (dev *fakeDevice) Write(p []byte) (int, error) { + return dev.buf.Write(p) +} + +func (dev *fakeDevice) Close() error { return nil } diff --git a/rpi/rpi.go b/rpi/rpi.go new file mode 100644 index 0000000..02ba261 --- /dev/null +++ b/rpi/rpi.go @@ -0,0 +1,58 @@ +// Copyright 2021 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rpi holds functions to manipulate data from RPi. +package rpi // import "github.com/go-lpc/mim/rpi" + +import ( + "fmt" + "strings" +) + +type DbInfo struct { + ID uint32 + NumASICs uint32 + Slow [][]byte // [MaxNumASICs][hardrocV2SLCFrameSize]byte +} + +func slcStatus(slc uint32) (string, bool) { + var ( + o = new(strings.Builder) + ok = true + ) + switch { + case slc&0x0003 == 0x01: + fmt.Fprintf(o, "SLC CRC OK - ") + case slc&0x0003 == 0x02: + fmt.Fprintf(o, "SLC CRC Failed - ") + ok = false + default: + fmt.Fprintf(o, "SLC CRC forb - ") + ok = false + } + + switch { + case slc&0x000c == 0x04: + fmt.Fprintf(o, "All OK - ") + case slc&0x000c == 0x08: + fmt.Fprintf(o, "All Failed - ") + ok = false + default: + fmt.Fprintf(o, "All forb - ") + ok = false + } + + switch { + case slc&0x0030 == 0x10: + fmt.Fprintf(o, "L1 OK - ") + case slc&0x0030 == 0x20: + fmt.Fprintf(o, "L1 Failed - ") + ok = false + default: + fmt.Fprintf(o, "L1 forb - ") + ok = false + } + + return o.String(), ok +} diff --git a/rpi/server.go b/rpi/server.go new file mode 100644 index 0000000..fef71e9 --- /dev/null +++ b/rpi/server.go @@ -0,0 +1,356 @@ +// Copyright 2020 The go-lpc Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rpi + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "github.com/go-daq/tdaq" + "github.com/ziutek/ftdi" +) + +type DeviceInfo struct { + VendorID uint32 + ProdID uint32 + Name string + ID uint32 + Type uint32 +} + +type Server struct { + name string + + difs []uint32 + devs map[uint32]DeviceInfo + rdos map[uint32]*Readout + + calib struct { + gain uint32 + thresholds [3]uint32 + } + + db map[uint32]*DbInfo +} + +func (srv *Server) scanDevices(ctx tdaq.Context) error { + srv.difs = srv.difs[:0] + srv.devs = make(map[uint32]DeviceInfo) + srv.db = make(map[uint32]*DbInfo) + + devs, err := ftdiListDevices(0x0403) + if err != nil { + return fmt.Errorf("could not build list of connected FTDI devices: %w", err) + } + + for _, dev := range devs { + ctx.Msg.Infof("found DIF 0x%x", dev.ProdID) + srv.difs = append(srv.difs, dev.ID) + srv.devs[dev.ID] = dev + } + + sort.Slice(srv.difs, func(i, j int) bool { + return srv.difs[i] < srv.difs[j] + }) + + return nil +} + +func (srv *Server) initialize(ctx tdaq.Context, id uint32) error { + if _, dup := srv.rdos[id]; dup { + ctx.Msg.Errorf("DIF 0x%x already registered", id) + return fmt.Errorf("DIF 0x%x already registered", id) + } + + dev, ok := srv.devs[id] + if !ok { + ctx.Msg.Errorf("DIF 0x%x not found in device map", id) + return fmt.Errorf("DIF 0x%x not found in device map", id) + } + + rdo, err := NewReadout(dev.Name, dev.ProdID, ctx.Msg) + if err != nil { + ctx.Msg.Errorf("could not create readout for DIF 0x%x (name=%q): %w", + id, dev.Name, err, + ) + return fmt.Errorf("could not create readout for DIF 0x%x (name=%q): %w", + id, dev.Name, err, + ) + } + defer func() { + if err != nil { + _ = rdo.close() + } + }() + + err = rdo.checkRW(0x1234, 100) + if err != nil { + ctx.Msg.Errorf("could not check r/w readout for DIF 0x%x (name=%q): %w", + id, dev.Name, err, + ) + return fmt.Errorf("could not check r/w readout for DIF 0x%x (name=%q): %w", + id, dev.Name, err, + ) + } + + srv.rdos[id] = rdo + ctx.Msg.Infof("readout for DIF 0x%x: OK", id) + + return nil +} + +func (srv *Server) preConfigure(ctx tdaq.Context, id, ctlreg uint32) error { + rdo, ok := srv.rdos[id] + if !ok { + return fmt.Errorf("could not find readout for DIF 0x%x", id) + } + rdo.setPowerManagment(0x8c52, 0x3e6, 0xd640, 0x4e, 0x4e) + err := rdo.dev.setControlRegister(ctlreg) + if err != nil { + return fmt.Errorf("could not set control register for readout 0x%x: %w", id, err) + } + err = rdo.configureRegisters() + if err != nil { + return fmt.Errorf("could not configure registers for readout 0x%x: %w", id, err) + } + + return nil +} + +func (srv *Server) configureChips(dif uint32, slow [][]byte, numASICs uint32) (uint32, error) { + rdo, ok := srv.rdos[dif] + if !ok || rdo == nil { + return 0, fmt.Errorf("could not find readout 0x%x", dif) + } + + if numASICs != MaxNumASICs { + rdo.nasics = int(numASICs) + err := rdo.configureRegisters() + if err != nil { + return 0, fmt.Errorf("could not configure registers with ASICs=%d for readout 0x%x: %w", + numASICs, dif, err, + ) + } + } + + return rdo.configureChips(slow) +} + +func (srv *Server) cmdScan(ctx tdaq.Context) error { + return srv.scanDevices(ctx) +} + +func (srv *Server) cmdInitialize(ctx tdaq.Context, req tdaq.Frame) error { + dec := tdaq.NewDecoder(bytes.NewReader(req.Body)) + difid := dec.ReadU32() + + err := srv.initialize(ctx, difid) + if err != nil { + return fmt.Errorf("could not intialize DIF 0x%x: %w", difid, err) + } + + return nil +} + +func (srv *Server) cmdRegisterState(ctx tdaq.Context, req tdaq.Frame) error { + dec := tdaq.NewDecoder(bytes.NewReader(req.Body)) + str := dec.ReadStr() + if str != "" { + panic("TODO") + } + // TODO(sbinet) + return nil +} + +func (srv *Server) cmdLoopConfigure(ctx tdaq.Context, req tdaq.Frame) error { + dec := tdaq.NewDecoder(bytes.NewReader(req.Body)) + id := dec.ReadU32() + n := int(dec.ReadU32()) + + db, ok := srv.db[id] + if !ok { + return fmt.Errorf("could not retrieve db-info for DIF 0x%x", id) + } + + if db.ID != id { + return fmt.Errorf("inconsistent db-info: id=0x%x|0x%x", db.ID, id) + } + + for i := 0; i < n; i++ { + slc, err := srv.configureChips(id, db.Slow, db.NumASICs) + if err != nil { + return fmt.Errorf("could not configure chips for DIF 0x%x: %w", id, err) + } + str, ok := slcStatus(slc) + ctx.Msg.Infof("slow control frame 0x%x: %s", id, str) + if !ok { + return fmt.Errorf("could not configure DIF 0x%x SLC=0x%x [%s]", + id, slc, str, + ) + } + } + + // TODO(sbinet) + return nil +} + +func (srv *Server) cmdPreconfigure(ctx tdaq.Context, req tdaq.Frame) error { + dec := tdaq.NewDecoder(bytes.NewReader(req.Body)) + ctlreg := dec.ReadU32() + + for _, id := range srv.difs { + err := srv.preConfigure(ctx, id, ctlreg) + if err != nil { + return fmt.Errorf( + "could not preconfigure readout 0x%x w/ ctlreg=0x%x: %w", + id, ctlreg, err, + ) + } + + db, ok := srv.db[id] + if !ok { + return fmt.Errorf("could not retrieve db-info for DIF 0x%x", id) + } + + slc, err := srv.configureChips(id, db.Slow, db.NumASICs) + if err != nil { + return fmt.Errorf("could not configure chips for DIF 0x%x: %w", id, err) + } + str, ok := slcStatus(slc) + ctx.Msg.Infof("slow control frame 0x%x: %s", id, str) + if !ok { + return fmt.Errorf("could not configure DIF 0x%x SLC=0x%x [%s]", + id, slc, str, + ) + } + + } + + return nil +} + +func (srv *Server) cmdConfigureChips(ctx tdaq.Context, req tdaq.Frame) error { + dec := tdaq.NewDecoder(bytes.NewReader(req.Body)) + dif := dec.ReadU32() + nasics := dec.ReadU32() + scframe := make([][]byte, nasics) + for i := range scframe { + scframe[i] = make([]byte, hardrocV2SLCFrameSize) + } + + srv.BOO + _, err := srv.configureChips(dif, scframe, nasics) + if err != nil { + return fmt.Errorf("could not configure chips for DIF=0x%x: %w", dif, err) + } + return nil +} + +func (srv *Server) OnConfig(ctx tdaq.Context, resp *tdaq.Frame, req tdaq.Frame) error { + ctx.Msg.Debugf("received /config command...") + err := srv.scanDevices(ctx) + if err != nil { + ctx.Msg.Errorf("could not scan devices: %+v", err) + return fmt.Errorf("could not scan devices: %w", err) + } + + return nil +} + +func (srv *Server) OnInit(ctx tdaq.Context, resp *tdaq.Frame, req tdaq.Frame) error { + ctx.Msg.Debugf("received /init command...") + for id := range srv.devs { + dev := srv.devs[id] + err := srv.initialize(ctx, dev.ID) + if err != nil { + ctx.Msg.Errorf("could not initialize DIF 0x%x: %+v", dev.ID, err) + return fmt.Errorf("could not initialize DIF 0x%x: %w", dev.ID, err) + } + } + return nil +} + +func (srv *Server) OnReset(ctx tdaq.Context, resp *tdaq.Frame, req tdaq.Frame) error { + ctx.Msg.Debugf("received /reset command...") + return nil +} + +func (srv *Server) OnStart(ctx tdaq.Context, resp *tdaq.Frame, req tdaq.Frame) error { + ctx.Msg.Debugf("received /start command...") + for _, id := range srv.difs { + rdo, ok := srv.rdos[id] + if !ok { + return fmt.Errorf("could not find rdo w/ DIF=0x%x", id) + } + + err := rdo.start() + if err != nil { + return fmt.Errorf("could not start readout for DIF=0x%x: %w", id, err) + } + } + return nil +} + +func (srv *Server) OnStop(ctx tdaq.Context, resp *tdaq.Frame, req tdaq.Frame) error { + ctx.Msg.Debugf("received /stop command...") + for _, id := range srv.difs { + rdo, ok := srv.rdos[id] + if !ok { + return fmt.Errorf("could not find rdo w/ DIF=0x%x", id) + } + + err := rdo.stop() + if err != nil { + return fmt.Errorf("could not stop readout for DIF=0x%x: %w", id, err) + } + } + return nil +} + +func (srv *Server) OnQuit(ctx tdaq.Context, resp *tdaq.Frame, req tdaq.Frame) error { + ctx.Msg.Debugf("received /quit command...") + return nil +} + +func ftdiListDevices(vid uint16) ([]DeviceInfo, error) { + var devs []DeviceInfo + + add := func(vid, pid uint16) { + lst, err := ftdi.FindAll(int(vid), int(pid)) + if err != nil { + return + } + for _, dev := range lst { + var ( + difid uint32 + dtype uint32 + ) + switch { + case strings.HasPrefix(dev.Serial, "FT101"): + fmt.Sscanf(dev.Serial, "FT101%d", &difid) + dtype = 0 + case strings.HasPrefix(dev.Serial, "DCCCCC"): + fmt.Sscanf(dev.Serial, "DCCCCC%d", &difid) + dtype = 0x10 + } + + devs = append(devs, DeviceInfo{ + VendorID: uint32(vid), + ProdID: uint32(pid), + Name: dev.Serial, + ID: difid, + Type: dtype, + }) + dev.Close() + } + } + + add(vid, 0x6001) // usb-1 + add(vid, 0x6014) // usb-2 + + return devs, nil +}