diff --git a/Makefile b/Makefile index 75544dd33..8fd72d3fc 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,10 @@ smoke-test: @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=arduino-nano33 ./examples/espat/espstation/main.go @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=itsybitsy-m0 ./examples/flash/console/spi + @md5sum ./build/test.hex + tinygo build -size short -o ./build/test.hex -target=pyportal ./examples/flash/console/qspi + @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/i2c/main.go @md5sum ./build/test.hex tinygo build -size short -o ./build/test.hex -target=feather-m0 ./examples/gps/uart/main.go diff --git a/README.md b/README.md index a55596566..6b262b812 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ func main() { ## Currently supported devices -The following 45 devices are supported. +The following 46 devices are supported. | Device Name | Interface Type | |----------|-------------| @@ -90,6 +90,7 @@ The following 45 devices are supported. | [Shift register (PISO)](https://en.wikipedia.org/wiki/Shift_register#Parallel-in_serial-out_\(PISO\)) | GPIO | | [Shift registers (SIPO)](https://en.wikipedia.org/wiki/Shift_register#Serial-in_parallel-out_(SIPO)) | GPIO | | [SHT3x Digital Humidity Sensor](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Humidity/Sensirion_Humidity_Sensors_SHT3x_Datasheet_digital.pdf) | I2C | +| [SPI NOR Flash Memory](https://en.wikipedia.org/wiki/Flash_memory#NOR_flash) | SPI/QSPI | | [SSD1306 OLED display](https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf) | I2C / SPI | | [SSD1331 TFT color display](https://www.crystalfontz.com/controllers/SolomonSystech/SSD1331/381/) | SPI | | [ST7735 TFT color display](https://www.crystalfontz.com/controllers/Sitronix/ST7735R/319/) | SPI | diff --git a/examples/flash/console/console_example.go b/examples/flash/console/console_example.go new file mode 100644 index 000000000..ee2f50f86 --- /dev/null +++ b/examples/flash/console/console_example.go @@ -0,0 +1,259 @@ +package console_example + +import ( + "fmt" + "io" + "machine" + "os" + "strconv" + "strings" + + "tinygo.org/x/drivers/flash" +) + +const consoleBufLen = 64 +const storageBufLen = 512 + +var ( + debug = false + + input [consoleBufLen]byte + store [storageBufLen]byte + + console = machine.UART0 + + dev *flash.Device + + commands map[string]cmdfunc = map[string]cmdfunc{ + "": cmdfunc(noop), + "erase": cmdfunc(erase), + "lsblk": cmdfunc(lsblk), + "write": cmdfunc(write), + "xxd": cmdfunc(xxd), + } +) + +type cmdfunc func(argv []string) + +const ( + StateInput = iota + StateEscape + StateEscBrc + StateCSI +) + +func RunFor(device *flash.Device) { + + dev = device + dev.Configure(&flash.DeviceConfig{ + Identifier: flash.DefaultDeviceIdentifier, + }) + + prompt() + + var state = StateInput + + for i := 0; ; { + if console.Buffered() > 0 { + data, _ := console.ReadByte() + if debug { + fmt.Printf("\rdata: %x\r\n\r", data) + prompt() + console.Write(input[:i]) + } + switch state { + case StateInput: + switch data { + case 0x8: + fallthrough + case 0x7f: // this is probably wrong... works on my machine tho :) + // backspace + if i > 0 { + i -= 1 + console.Write([]byte{0x8, 0x20, 0x8}) + } + case 13: + // return key + console.Write([]byte("\r\n")) + runCommand(string(input[:i])) + prompt() + + i = 0 + continue + case 27: + // escape + state = StateEscape + default: + // anything else, just echo the character if it is printable + if strconv.IsPrint(rune(data)) { + if i < (consoleBufLen - 1) { + console.WriteByte(data) + input[i] = data + i++ + } + } + } + case StateEscape: + switch data { + case 0x5b: + state = StateEscBrc + default: + state = StateInput + } + default: + // TODO: handle escape sequences + state = StateInput + } + } + } +} + +func runCommand(line string) { + argv := strings.SplitN(strings.TrimSpace(line), " ", -1) + cmd := argv[0] + cmdfn, ok := commands[cmd] + if !ok { + println("unknown command: " + line) + return + } + cmdfn(argv) +} + +func noop(argv []string) {} + +func lsblk(argv []string) { + attrs := dev.Attrs() + status1, _ := dev.ReadStatus() + status2, _ := dev.ReadStatus2() + serialNumber1, _ := dev.ReadSerialNumber() + fmt.Printf( + "\n-------------------------------------\r\n"+ + " Device Information: \r\n"+ + "-------------------------------------\r\n"+ + " JEDEC ID: %v\r\n"+ + " Serial: %v\r\n"+ + " Status 1: %02x\r\n"+ + " Status 2: %02x\r\n"+ + " \r\n"+ + " Max clock speed (MHz): %d\r\n"+ + " Has Sector Protection: %t\r\n"+ + " Supports Fast Reads: %t\r\n"+ + " Supports QSPI Reads: %t\r\n"+ + " Supports QSPI Write: %t\r\n"+ + " Write Status Split: %t\r\n"+ + " Single Status Byte: %t\r\n"+ + "-------------------------------------\r\n\r\n", + attrs.JedecID, + serialNumber1, + status1, + status2, + attrs.MaxClockSpeedMHz, + attrs.HasSectorProtection, + attrs.SupportsFastRead, + attrs.SupportsQSPI, + attrs.SupportsQSPIWrites, + attrs.WriteStatusSplit, + attrs.SingleStatusByte, + ) +} + +func erase(argv []string) { + if len(argv) < 3 { + println("usage: erase ") + return + } + var err error + var addr uint64 = 0x0 + if addr, err = strconv.ParseUint(argv[2], 16, 32); err != nil { + println("Invalid address: " + err.Error() + "\r\n") + return + } + if argv[1] == "block" { + if err = dev.EraseBlock(uint32(addr)); err != nil { + println("Block erase error: " + err.Error() + "\r\n") + } + } else if argv[1] == "sector" { + if err = dev.EraseSector(uint32(addr)); err != nil { + println("Sector erase error: " + err.Error() + "\r\n") + } + } else if argv[1] == "chip" { + if err = dev.EraseAll(); err != nil { + println("Chip erase error: " + err.Error() + "\r\n") + } + } else { + println("usage: erase ") + } +} + +func write(argv []string) { + if len(argv) < 3 { + println("usage: write ") + } + var err error + var addr uint64 = 0x0 + if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil { + println("Invalid address: " + err.Error() + "\r\n") + return + } + buf := []byte(argv[2]) + if _, err = dev.WriteAt(buf, int64(addr)); err != nil { + println("Write error: " + err.Error() + "\r\n") + } +} + +func xxd(argv []string) { + var err error + var addr uint64 = 0x0 + var size int = 64 + switch len(argv) { + case 3: + if size, err = strconv.Atoi(argv[2]); err != nil { + println("Invalid size argument: " + err.Error() + "\r\n") + return + } + if size > storageBufLen || size < 1 { + fmt.Printf("Size of hexdump must be greater than 0 and less than %d\r\n", storageBufLen) + return + } + fallthrough + case 2: + if addr, err = strconv.ParseUint(argv[1], 16, 32); err != nil { + println("Invalid address: " + err.Error() + "\r\n") + return + } + fallthrough + case 1: + // no args supplied, so nothing to do here, just use the defaults + default: + println("usage: xxd \r\n") + return + } + buf := store[0:size] + dev.ReadAt(buf, int64(addr)) + xxdfprint(os.Stdout, uint32(addr), buf) +} + +func xxdfprint(w io.Writer, offset uint32, b []byte) { + var l int + var buf16 = make([]byte, 16) + for i, c := 0, len(b); i < c; i += 16 { + l = i + 16 + if l >= c { + l = c + } + fmt.Fprintf(w, "%08x: % x ", offset+uint32(i), b[i:l]) + for j, n := 0, l-i; j < 16; j++ { + if j >= n || !strconv.IsPrint(rune(b[i+j])) { + buf16[j] = '.' + } else { + buf16[j] = b[i+j] + } + } + console.Write(buf16) + println() + } +} + +func prompt() { + print("==> ") +} diff --git a/examples/flash/console/qspi/main.go b/examples/flash/console/qspi/main.go new file mode 100644 index 000000000..6a71931e3 --- /dev/null +++ b/examples/flash/console/qspi/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "machine" + + "tinygo.org/x/drivers/examples/flash/console" + "tinygo.org/x/drivers/flash" +) + +func main() { + console_example.RunFor( + flash.NewQSPI( + machine.QSPI_CS, + machine.QSPI_SCK, + machine.QSPI_DATA0, + machine.QSPI_DATA1, + machine.QSPI_DATA2, + machine.QSPI_DATA3, + ), + ) +} diff --git a/examples/flash/console/spi/main.go b/examples/flash/console/spi/main.go new file mode 100644 index 000000000..b2e0bb1cd --- /dev/null +++ b/examples/flash/console/spi/main.go @@ -0,0 +1,20 @@ +package main + +import ( + "machine" + + "tinygo.org/x/drivers/examples/flash/console" + "tinygo.org/x/drivers/flash" +) + +func main() { + console_example.RunFor( + flash.NewSPI( + &machine.SPI1, + machine.SPI1_MOSI_PIN, + machine.SPI1_MISO_PIN, + machine.SPI1_SCK_PIN, + machine.SPI1_CS_PIN, + ), + ) +} diff --git a/flash/devices.go b/flash/devices.go new file mode 100644 index 000000000..69239a62f --- /dev/null +++ b/flash/devices.go @@ -0,0 +1,448 @@ +package flash + +import "time" + +// A DeviceIdentifier can be passed to the Configure() method of a flash Device +// in order provide a means of discovery of device-specific attributes based on +// the JEDEC ID read from the device. +type DeviceIdentifier interface { + // Identify returns an Attrs struct based on the provided JEDEC ID + Identify(id JedecID) Attrs +} + +// DeviceIdentifierFunc is a functional Identifier implementation +type DeviceIdentifierFunc func(id JedecID) Attrs + +// Identify implements the Identifier interface +func (fn DeviceIdentifierFunc) Identify(id JedecID) Attrs { + return fn(id) +} + +// DefaultDeviceIndentifier is a DeviceIdentifier that is capable of recognizing +// JEDEC IDs for all of the known memory devices in this package. If you are +// have no way to be sure about the type of memory device that might be on a +// board you are targeting, this can be a good starting point to use. The +// downside of using this function is that it will prevent the compiler from +// being able to mark any of the functions for the various devices as unused, +// resulting in larger code size. If code size is a concern, and if you know +// ahead of time you are only dealing with a limited set of memory devices, it +// might be worthwhile to use your own implementation of a DeviceIdentifier +// that only references those devices, so that more methods are marked unused. +var DefaultDeviceIdentifier = DeviceIdentifierFunc(func(id JedecID) Attrs { + switch id.Uint32() { + case 0x010617: + return S25FL064L() + case 0x014015: + return S25FL216K() + case 0x1F4501: + return AT25DF081A() + case 0xC22015: + return MX25L1606() + case 0xC22016: + return MX25L3233F() + case 0xC22817: + return MX25R6435F() + case 0xC84015: + return GD25Q16C() + case 0xC84017: + return GD25Q64C() + case 0xEF4015: + return W25Q16JVIQ() + case 0xEF4016: + return W25Q32FV() + case 0xEF4017: + return W25Q64JVIQ() + case 0xEF4018: + return W25Q128JVSQ() + case 0xEF6014: + return W25Q80DL() + case 0xEF6015: + return W25Q16FW() + case 0xEF6016: + return W25Q32BV() + case 0xEF7015: + return W25Q16JVIM() + case 0xEF7016: + return W25Q32JVIM() + case 0xEF7017: + return W25Q64JVIM() + case 0xEF7018: + return W25Q128JVPM() + default: + return Attrs{JedecID: id} + } +}) + +// Settings for the Cypress (was Spansion) S25FL064L 8MiB SPI flash. +// Datasheet: http://www.cypress.com/file/316661/download +func S25FL064L() Attrs { + return Attrs{ + TotalSize: 1 << 23, // 8 MiB + StartUp: 300 * time.Microsecond, + JedecID: JedecID{0x01, 0x60, 0x17}, + MaxClockSpeedMHz: 108, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Cypress (was Spansion) S25FL116K 2MiB SPI flash. +// Datasheet: http://www.cypress.com/file/196886/download +func S25FL116K() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB + StartUp: 10000 * time.Microsecond, + JedecID: JedecID{0x01, 0x40, 0x15}, + MaxClockSpeedMHz: 108, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: false, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Cypress (was Spansion) S25FL216K 2MiB SPI flash. +// Datasheet: http://www.cypress.com/file/197346/download +func S25FL216K() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB + StartUp: 10000 * time.Microsecond, + JedecID: JedecID{0x01, 0x40, 0x15}, + MaxClockSpeedMHz: 65, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: false, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Adesto Tech AT25DF081A 1MiB SPI flash. Its on the SAMD21 +// Xplained board. +// Datasheet: https://www.adestotech.com/wp-content/uploads/doc8715.pdf +func AT25DF081A() Attrs { + return Attrs{ + TotalSize: 1 << 20, // 1 MiB + StartUp: 10000 * time.Microsecond, + JedecID: JedecID{0x1F, 0x45, 0x01}, + MaxClockSpeedMHz: 85, + QuadEnableBitMask: 0x00, + HasSectorProtection: true, + SupportsFastRead: true, + SupportsQSPI: false, + SupportsQSPIWrites: false, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Macronix MX25L1606 2MiB SPI flash. +// Datasheet: +func MX25L1606() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB, + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xC2, 0x20, 0x15}, + MaxClockSpeedMHz: 8, + QuadEnableBitMask: 0x40, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: true, + } +} + +// Settings for the Macronix MX25L3233F 4MiB SPI flash. +// Datasheet: +// http://www.macronix.com/Lists/Datasheet/Attachments/7426/MX25L3233F,%203V,%2032Mb,%20v1.6.pdf +func MX25L3233F() Attrs { + return Attrs{ + TotalSize: 1 << 22, // 4 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xC2, 0x20, 0x16}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x40, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Macronix MX25R6435F 8MiB SPI flash. +// Datasheet: +// http://www.macronix.com/Lists/Datasheet/Attachments/7428/MX25R6435F,%20Wide%20Range,%2064Mb,%20v1.4.pdf +// By default its in lower power mode which can only do 8mhz. In high power mode +// it can do 80mhz. +func MX25R6435F() Attrs { + return Attrs{ + TotalSize: 1 << 23, // 8 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xC2, 0x28, 0x17}, + MaxClockSpeedMHz: 8, + QuadEnableBitMask: 0x40, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: true, + } +} + +// Settings for the Gigadevice GD25Q16C 2MiB SPI flash. +// Datasheet: http://www.gigadevice.com/datasheet/gd25q16c/ +func GD25Q16C() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xC8, 0x40, 0x15}, + MaxClockSpeedMHz: 104, + QuadEnableBitMask: 0x02, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Gigadevice GD25Q64C 8MiB SPI flash. +// Datasheet: http://www.elm-tech.com/en/products/spi-flash-memory/gd25q64/gd25q64.pdf +func GD25Q64C() Attrs { + return Attrs{ + TotalSize: 1 << 23, // 8 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xC8, 0x40, 0x17}, + MaxClockSpeedMHz: 104, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: true, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q16JV-IQ 2MiB SPI flash. Note that JV-IM has a +// different .memory_type (0x70) Datasheet: +// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf +func W25Q16JVIQ() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x40, 0x15}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q16FW 2MiB SPI flash. +// Datasheet: +// https://www.winbond.com/resource-files/w25q16fw%20revj%2005182017%20sfdp.pdf +func W25Q16FW() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x60, 0x15}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q16JV-IM 2MiB SPI flash. Note that JV-IQ has a +// different .memory_type (0x40) Datasheet: +// https://www.winbond.com/resource-files/w25q16jv%20spi%20revf%2005092017.pdf +func W25Q16JVIM() Attrs { + return Attrs{ + TotalSize: 1 << 21, // 2 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x70, 0x15}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q32BV 4MiB SPI flash. +// Datasheet: +// https://www.winbond.com/resource-files/w25q32bv_revi_100413_wo_automotive.pdf +func W25Q32BV() Attrs { + return Attrs{ + TotalSize: 1 << 22, // 4 MiB + StartUp: 10000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x60, 0x16}, + MaxClockSpeedMHz: 104, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: false, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q32JV-IM 4MiB SPI flash. +// Datasheet: +// https://www.winbond.com/resource-files/w25q32jv%20revg%2003272018%20plus.pdf +func W25Q32JVIM() Attrs { + return Attrs{ + TotalSize: 1 << 22, // 4 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x70, 0x16}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q64JV-IM 8MiB SPI flash. Note that JV-IQ has a +// different .memory_type (0x40) Datasheet: +// http://www.winbond.com/resource-files/w25q64jv%20revj%2003272018%20plus.pdf +func W25Q64JVIM() Attrs { + return Attrs{ + TotalSize: 1 << 23, // 8 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x70, 0x17}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q64JV-IQ 8MiB SPI flash. Note that JV-IM has a +// different .memory_type (0x70) Datasheet: +// http://www.winbond.com/resource-files/w25q64jv%20revj%2003272018%20plus.pdf +func W25Q64JVIQ() Attrs { + return Attrs{ + TotalSize: 1 << 23, // 8 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x40, 0x17}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q80DL 1MiB SPI flash. +// Datasheet: +// https://www.winbond.com/resource-files/w25q80dv%20dl_revh_10022015.pdf +func W25Q80DL() Attrs { + return Attrs{ + TotalSize: 1 << 20, // 1 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x60, 0x14}, + MaxClockSpeedMHz: 104, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: false, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q128JV-SQ 16MiB SPI flash. Note that JV-IM has a +// different .memory_type (0x70) Datasheet: +// https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf +func W25Q128JVSQ() Attrs { + return Attrs{ + TotalSize: 1 << 24, // 16 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x40, 0x18}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q128JV-PM 16MiB SPI flash. Note that JV-IM has a +// different .memory_type (0x70) Datasheet: +// https://www.winbond.com/resource-files/w25q128jv%20revf%2003272018%20plus.pdf +func W25Q128JVPM() Attrs { + return Attrs{ + TotalSize: 1 << 24, // 16 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x70, 0x18}, + MaxClockSpeedMHz: 133, + QuadEnableBitMask: 0x02, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: true, + SupportsQSPIWrites: true, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} + +// Settings for the Winbond W25Q32FV 4MiB SPI flash. +// Datasheet:http://www.winbond.com/resource-files/w25q32fv%20revj%2006032016.pdf?__locale=en +func W25Q32FV() Attrs { + return Attrs{ + TotalSize: 1 << 22, // 4 MiB + StartUp: 5000 * time.Microsecond, + JedecID: JedecID{0xEF, 0x40, 0x16}, + MaxClockSpeedMHz: 104, + QuadEnableBitMask: 0x00, + HasSectorProtection: false, + SupportsFastRead: true, + SupportsQSPI: false, + SupportsQSPIWrites: false, + WriteStatusSplit: false, + SingleStatusByte: false, + } +} diff --git a/flash/flash.go b/flash/flash.go new file mode 100644 index 000000000..aa26bc6c8 --- /dev/null +++ b/flash/flash.go @@ -0,0 +1,405 @@ +package flash + +import ( + "time" +) + +const ( + // BlockSize is the number of bytes in a block for most/all NOR flash memory + BlockSize = 64 * 1024 + + // SectorSize is the number of bytes in a sector for most/all NOR flash memory + SectorSize = 4 * 1024 + + // PageSize is the number of bytes in a page for most/all NOR flash memory + PageSize = 256 +) + +// Device represents a NOR flash memory device accessible using SPI +type Device struct { + trans transport + attrs Attrs +} + +// DeviceConfig contains the parameters that can be set when configuring a +// flash memory device. +type DeviceConfig struct { + Identifier DeviceIdentifier +} + +// JedecID encapsules the ID values that unique identify a flash memory device. +type JedecID struct { + ManufID uint8 + MemType uint8 + Capacity uint8 +} + +// Uint32 returns the JEDEC ID packed into a uint32 +func (id JedecID) Uint32() uint32 { + return uint32(id.ManufID)<<16 | uint32(id.MemType)<<8 | uint32(id.Capacity) +} + +// SerialNumber represents a serial number read from a flash memory device +type SerialNumber uint64 + +// Attrs represent the differences in hardware characteristics and capabilities +// of various SPI flash memory devices. +type Attrs struct { + + // TotalSize is the number of bytes that the flash device can store + TotalSize uint32 + + // StartUp is the duration of time between when the device is reset and when + // it is ready to operation + StartUp time.Duration + + // Three response bytes to 0x9f JEDEC ID command. + JedecID + + // Max clock speed for all operations and the fastest read mode. + MaxClockSpeedMHz uint8 + + // Bitmask for Quad Enable bit if present. 0x00 otherwise. This is for the + // highest byte in the status register. + QuadEnableBitMask uint8 + + HasSectorProtection bool + + // Supports the 0x0b fast read command with 8 dummy cycles. + SupportsFastRead bool + + // Supports the fast read, quad output command 0x6b with 8 dummy cycles. + SupportsQSPI bool + + // Supports the quad input page program command 0x32. This is known as 1-1-4 + // because it only uses all four lines for data. + SupportsQSPIWrites bool + + // Requires a separate command 0x31 to write to the second byte of the status + // register. Otherwise two byte are written via 0x01. + WriteStatusSplit bool + + // True when the status register is a single byte. This implies the Quad + // Enable bit is in the first byte and the Read Status Register 2 command + // (0x35) is unsupported. + SingleStatusByte bool +} + +// Configure sets up the device and the underlying transport mechanism. The +// DeviceConfig argument allows the caller to specify an instance of the +// DeviceIdentifier interface that, if provided, will be used to retrieve the +// attributes of the device based on the JEDEC ID. +func (dev *Device) Configure(config *DeviceConfig) (err error) { + + dev.trans.configure(config) + + var id JedecID + if id, err = dev.ReadJEDEC(); err != nil { + return err + } + + // try to ascertain the vendor-specific attributes of the chip using the + // provided Identifier + if config.Identifier != nil { + dev.attrs = config.Identifier.Identify(id) + } else { + dev.attrs = Attrs{JedecID: id} + } + + // We don't know what state the flash is in so wait for any remaining + // writes and then reset. + + // The write in progress bit should be low. + for s, err := dev.ReadStatus(); (s & 0x01) > 0; s, err = dev.ReadStatus() { + if err != nil { + return err + } + } + // The suspended write/erase bit should be low. + for s, err := dev.ReadStatus2(); (s & 0x80) > 0; s, err = dev.ReadStatus2() { + if err != nil { + return err + } + } + // perform device reset + if err := dev.trans.runCommand(cmdEnableReset); err != nil { + return err + } + if err := dev.trans.runCommand(cmdReset); err != nil { + return err + } + + // Wait for the reset - 30us by default + time.Sleep(30 * time.Microsecond) + + // Speed up to max device frequency + if dev.attrs.MaxClockSpeedMHz > 0 { + err := dev.trans.setClockSpeed(uint32(dev.attrs.MaxClockSpeedMHz) * 1e6) + if err != nil { + return err + } + } + + // Enable Quad Mode if available + if dev.trans.supportQuadMode() && dev.attrs.QuadEnableBitMask > 0 { + // Verify that QSPI mode is enabled. + var status byte + if dev.attrs.SingleStatusByte { + status, err = dev.ReadStatus() + } else { + status, err = dev.ReadStatus2() + } + if err != nil { + return err + } + // Check and set the quad enable bit. + if status&dev.attrs.QuadEnableBitMask == 0 { + if err := dev.WriteEnable(); err != nil { + return err + } + fullStatus := []byte{0x00, dev.attrs.QuadEnableBitMask} + if dev.attrs.WriteStatusSplit { + err = dev.trans.writeCommand(cmdWriteStatus2, fullStatus[1:]) + } else if dev.attrs.SingleStatusByte { + err = dev.trans.writeCommand(cmdWriteStatus, fullStatus[1:]) + } else { + err = dev.trans.writeCommand(cmdWriteStatus, fullStatus) + } + if err != nil { + return err + } + } + } + + // disable sector protection if the chip has it + if dev.attrs.HasSectorProtection { + if err := dev.WriteEnable(); err != nil { + return err + } + if err := dev.trans.writeCommand(cmdWriteStatus, []byte{0x00}); err != nil { + return err + } + } + + // write disable + if err := dev.trans.runCommand(cmdWriteDisable); err != nil { + return err + } + return dev.WaitUntilReady() +} + +// Attrs returns the attributes of the device determined from the most recent +// call to Configure(). If no call to Configure() has been made, this will be +// the zero value of the Attrs struct. +func (dev *Device) Attrs() Attrs { + return dev.attrs +} + +// ReadJEDEC reads the JEDEC ID from the device; this ID can then be used to +// ascertain the attributes of the chip from a list of known devices. +func (dev *Device) ReadJEDEC() (JedecID, error) { + jedecID := make([]byte, 3) + if err := dev.trans.readCommand(cmdReadJedecID, jedecID); err != nil { + return JedecID{}, err + } + return JedecID{jedecID[0], jedecID[1], jedecID[2]}, nil +} + +// ReadSerialNumber reads the serial numbers from the connected device. +// TODO: maybe check if byte order / endianess is correct, probably is not +func (dev *Device) ReadSerialNumber() (SerialNumber, error) { + sn := make([]byte, 12) + if err := dev.trans.readCommand(0x4B, sn); err != nil { + return 0, err + } + return SerialNumber(uint64(sn[11]) | uint64(sn[10])<<0x8 | + uint64(sn[9])<<0x10 | uint64(sn[8])<<0x18 | uint64(sn[7])<<0x20 | + uint64(sn[6])<<0x28 | uint64(sn[5])<<0x30 | uint64(sn[4])<<0x38), nil +} + +// Size returns the size of this memory, in bytes. +func (dev *Device) Size() int64 { + if dev.attrs.TotalSize < 1 { + // in case a DeviceIdentifier function wasn't used, use the capacity + // specified in the JEDEC ID instead + return int64(dev.attrs.Capacity) + } + return int64(dev.attrs.TotalSize) +} + +// ReadAt satisfies the io.ReaderAt interface, and fills the provided buffer +// with memory read from the device starting at the provided address. +func (dev *Device) ReadAt(buf []byte, addr int64) (int, error) { + if err := dev.WaitUntilReady(); err != nil { + return 0, err + } + if err := dev.trans.readMemory(uint32(addr), buf); err != nil { + return 0, err + } + return len(buf), nil +} + +// WriteAt satisfies the io.WriterAt interface and writes data to the device, +// one page at a time, starting at the provided address. This method assumes +// that the destination is already erased. +func (dev *Device) WriteAt(buf []byte, addr int64) (n int, err error) { + remain := uint32(len(buf)) + idx := uint32(0) + loc := uint32(addr) + for remain > 0 { + if err = dev.WaitUntilReady(); err != nil { + return + } + if err = dev.WriteEnable(); err != nil { + return + } + leftOnPage := PageSize - (loc & (PageSize - 1)) + toWrite := remain + if leftOnPage < remain { + toWrite = leftOnPage + } + if err = dev.trans.writeMemory(loc, buf[idx:idx+toWrite]); err != nil { + return + } + idx += toWrite + loc += toWrite + remain -= toWrite + } + return len(buf) - int(remain), nil +} + +// WriteBlockSize returns the block size in which data can be written to +// memory. It can be used by a client to optimize writes, non-aligned writes +// should always work correctly. +// For SPI NOR flash this is the page size, usually/always 256. +func (dev *Device) WriteBlockSize() int64 { + return PageSize +} + +// EraseBlockSize returns the smallest erasable area on this particular chip +// in bytes. This is used for the block size in EraseBlocks. +// For SPI NOR flash this is the sector size, usually/always 4096. +func (dev *Device) EraseBlockSize() int64 { + return SectorSize +} + +// EraseBlocks erases the given number of blocks. An implementation may +// transparently coalesce ranges of blocks into larger bundles if the chip +// supports this. The start and len parameters are in block numbers, use +// EraseBlockSize to map addresses to blocks. +func (dev *Device) EraseBlocks(start, len int64) error { + // TODO: maybe combine sector erase operations into block erase operations + for i := start; i < start+len; i++ { + if err := dev.EraseSector(uint32(i)); err != nil { + return err + } + } + return nil +} + +func (dev *Device) WriteEnable() error { + return dev.trans.runCommand(cmdWriteEnable) +} + +// EraseBlock erases a block of memory at the specified index +func (dev *Device) EraseBlock(blockNumber uint32) error { + if err := dev.WaitUntilReady(); err != nil { + return err + } + if err := dev.WriteEnable(); err != nil { + return err + } + return dev.trans.eraseCommand(cmdEraseBlock, blockNumber*BlockSize) +} + +// EraseSector erases a sector of memory at the given index +func (dev *Device) EraseSector(sectorNumber uint32) error { + if err := dev.WaitUntilReady(); err != nil { + return err + } + if err := dev.WriteEnable(); err != nil { + return err + } + return dev.trans.eraseCommand(cmdEraseSector, sectorNumber*SectorSize) +} + +// EraseChip erases the entire flash memory chip +func (dev *Device) EraseAll() error { + if err := dev.WaitUntilReady(); err != nil { + return err + } + if err := dev.WriteEnable(); err != nil { + return err + } + return dev.trans.runCommand(cmdEraseChip) +} + +// ReadStatus reads the value from status register 1 of the device +func (dev *Device) ReadStatus() (status byte, err error) { + buf := make([]byte, 1) + err = dev.trans.readCommand(cmdReadStatus, buf) + return buf[0], err +} + +// ReadStatus2 reads the value from status register 2 of the device +func (dev *Device) ReadStatus2() (status byte, err error) { + buf := make([]byte, 1) + err = dev.trans.readCommand(cmdReadStatus2, buf) + return buf[0], err +} + +// WaitUntilReady queries the status register until the device is ready for the +// next operation. +func (dev *Device) WaitUntilReady() error { + expire := time.Now().UnixNano() + int64(1*time.Second) + for s, err := dev.ReadStatus(); (s & 0x03) > 0; s, err = dev.ReadStatus() { + if err != nil { + return err + } + if time.Now().UnixNano() > expire { + return ErrWaitExpired + } + } + return nil +} + +const ( + cmdRead = 0x03 // read memory using single-bit transfer + cmdQuadRead = 0x6B // read with 1 line address, 4 line data + cmdReadJedecID = 0x9F // read the JEDEC ID from the device + cmdPageProgram = 0x02 // write a page of memory using single-bit transfer + cmdQuadPageProgram = 0x32 // write with 1 line address, 4 line data + cmdReadStatus = 0x05 // read status register 1 + cmdReadStatus2 = 0x35 // read status register 2 + cmdWriteStatus = 0x01 // write status register 1 + cmdWriteStatus2 = 0x31 // write status register 2 + cmdEnableReset = 0x66 // enable reset + cmdReset = 0x99 // perform reset + cmdWriteEnable = 0x06 // write-enable memory + cmdWriteDisable = 0x04 // write-protect memory + cmdEraseSector = 0x20 // erase a sector of memory + cmdEraseBlock = 0xD8 // erase a block of memory + cmdEraseChip = 0xC7 // erase the entire chip +) + +type Error uint8 + +const ( + _ = iota + ErrInvalidClockSpeed Error = iota + ErrInvalidAddrRange + ErrWaitExpired +) + +func (err Error) Error() string { + switch err { + case ErrInvalidClockSpeed: + return "flash: invalid clock speed" + case ErrInvalidAddrRange: + return "flash: invalid address range" + case ErrWaitExpired: + return "flash: wait until ready expired" + default: + return "flash: unspecified error" + } +} diff --git a/flash/transport_qspi_samd.go b/flash/transport_qspi_samd.go new file mode 100644 index 000000000..63ce00918 --- /dev/null +++ b/flash/transport_qspi_samd.go @@ -0,0 +1,247 @@ +// +build atsamd51 + +package flash + +import ( + "device/sam" + "machine" + "runtime/volatile" + "unsafe" +) + +// NewQSPI returns a pointer to a flash device that uses the QSPI peripheral to +// communicate with a serial memory chip. +func NewQSPI(cs, sck, d0, d1, d2, d3 machine.Pin) *Device { + return &Device{ + trans: &qspiTransport{ + cs: cs, + sck: sck, + d0: d0, + d1: d1, + d2: d2, + d3: d3, + }, + } +} + +// QSPI address space on SAMD51 is 0x04000000 to 0x05000000 +const ( + // Low address of the QSPI address space on SAMD51 + qspi_AHB_LO = 0x04000000 + + // High address of the QSPI address space on SAMD51 + qspi_AHB_HI = 0x05000000 + + // Instruction frame for running sending a command to the device + iframeRunCommand = 0x0 | + sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + sam.QSPI_INSTRFRAME_ADDRLEN_24BITS | + sam.QSPI_INSTRFRAME_INSTREN | + (sam.QSPI_INSTRFRAME_TFRTYPE_READ << sam.QSPI_INSTRFRAME_TFRTYPE_Pos) + + // Instruction frame for running a command that returns data + iframeReadCommand = 0x0 | + sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + sam.QSPI_INSTRFRAME_ADDRLEN_24BITS | + sam.QSPI_INSTRFRAME_INSTREN | + sam.QSPI_INSTRFRAME_DATAEN | + (sam.QSPI_INSTRFRAME_TFRTYPE_READ << sam.QSPI_INSTRFRAME_TFRTYPE_Pos) + + // Instruction frame to set up the device to read from memory + iframeReadMemory = 0x0 | + sam.QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | + sam.QSPI_INSTRFRAME_ADDRLEN_24BITS | + sam.QSPI_INSTRFRAME_INSTREN | + sam.QSPI_INSTRFRAME_DATAEN | + sam.QSPI_INSTRFRAME_ADDREN | + (8 << sam.QSPI_INSTRFRAME_DUMMYLEN_Pos) | + (sam.QSPI_INSTRFRAME_TFRTYPE_READMEMORY << sam.QSPI_INSTRFRAME_TFRTYPE_Pos) + + // Instruction frame for running a command that requires parameter data + iframeWriteCommand = 0x0 | + sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + sam.QSPI_INSTRFRAME_ADDRLEN_24BITS | + sam.QSPI_INSTRFRAME_INSTREN | + (sam.QSPI_INSTRFRAME_TFRTYPE_WRITE << sam.QSPI_INSTRFRAME_TFRTYPE_Pos) + + // Instruction frame to set up the device for writing to memory + iframeWriteMemory = 0x0 | + sam.QSPI_INSTRFRAME_WIDTH_QUAD_OUTPUT | + sam.QSPI_INSTRFRAME_ADDRLEN_24BITS | + sam.QSPI_INSTRFRAME_INSTREN | + sam.QSPI_INSTRFRAME_ADDREN | + sam.QSPI_INSTRFRAME_DATAEN | + (sam.QSPI_INSTRFRAME_TFRTYPE_WRITEMEMORY << sam.QSPI_INSTRFRAME_TFRTYPE_Pos) + + // Instruction frame for running an erase command that requires and address + iframeEraseCommand = 0x0 | + sam.QSPI_INSTRFRAME_WIDTH_SINGLE_BIT_SPI | + sam.QSPI_INSTRFRAME_ADDRLEN_24BITS | + sam.QSPI_INSTRFRAME_INSTREN | + sam.QSPI_INSTRFRAME_ADDREN | + (sam.QSPI_INSTRFRAME_TFRTYPE_WRITE << sam.QSPI_INSTRFRAME_TFRTYPE_Pos) +) + +type qspiTransport struct { + cs machine.Pin + sck machine.Pin + d0 machine.Pin + d1 machine.Pin + d2 machine.Pin + d3 machine.Pin +} + +func (q qspiTransport) configure(config *DeviceConfig) { + + // enable main clocks + sam.MCLK.APBCMASK.SetBits(sam.MCLK_APBCMASK_QSPI_) + sam.MCLK.AHBMASK.SetBits(sam.MCLK_AHBMASK_QSPI_) + sam.MCLK.AHBMASK.ClearBits(sam.MCLK_AHBMASK_QSPI_2X_) + + sam.QSPI.CTRLA.SetBits(sam.QSPI_CTRLA_SWRST) + + // enable all pins to be PinCom + q.d0.Configure(machine.PinConfig{Mode: machine.PinCom}) + q.d1.Configure(machine.PinConfig{Mode: machine.PinCom}) + q.d2.Configure(machine.PinConfig{Mode: machine.PinCom}) + q.d3.Configure(machine.PinConfig{Mode: machine.PinCom}) + q.cs.Configure(machine.PinConfig{Mode: machine.PinCom}) + q.sck.Configure(machine.PinConfig{Mode: machine.PinCom}) + + // start out with 4Mhz + // can ignore the error, 4Mhz is always a valid speed + _ = q.setClockSpeed(4e6) + + // configure the CTRLB register + sam.QSPI.CTRLB.Set(sam.QSPI_CTRLB_MODE_MEMORY | + (sam.QSPI_CTRLB_DATALEN_8BITS << sam.QSPI_CTRLB_DATALEN_Pos) | + (sam.QSPI_CTRLB_CSMODE_LASTXFER << sam.QSPI_CTRLB_CSMODE_Pos)) + + // enable the peripheral + sam.QSPI.CTRLA.SetBits(sam.QSPI_CTRLA_ENABLE) +} + +func (q qspiTransport) supportQuadMode() bool { + return true +} + +func (q qspiTransport) setClockSpeed(hz uint32) error { + // The clock speed for the QSPI peripheral is controlled by a divider, so + // we can't set the requested speed exactly. Instead we will increment the + // divider until the speed is less than or equal to the speed requested. + for div, freq := uint32(1), machine.CPUFrequency(); div < 256; div++ { + if freq/div <= hz { + sam.QSPI.BAUD.Set(div << sam.QSPI_BAUD_BAUD_Pos) + return nil + } + } + return ErrInvalidClockSpeed +} + +func (q qspiTransport) runCommand(cmd byte) (err error) { + q.runInstruction(cmd, iframeRunCommand) + q.endTransfer() + return +} + +func (q qspiTransport) readCommand(cmd byte, buf []byte) (err error) { + q.disableAndClearCache() + q.runInstruction(cmd, iframeReadCommand) + q.readInto(buf, 0) + q.endTransfer() + q.enableCache() + return +} + +func (q qspiTransport) readMemory(addr uint32, buf []byte) (err error) { + if (addr + uint32(len(buf))) > (qspi_AHB_HI - qspi_AHB_LO) { + return ErrInvalidAddrRange + } + q.disableAndClearCache() + q.runInstruction(cmdQuadRead, iframeReadMemory) + q.readInto(buf, addr) + q.endTransfer() + q.enableCache() + return +} + +func (q qspiTransport) writeCommand(cmd byte, data []byte) (err error) { + var dataen uint32 + if len(data) > 0 { + dataen = sam.QSPI_INSTRFRAME_DATAEN + } + q.disableAndClearCache() + q.runInstruction(cmd, iframeWriteCommand|dataen) + q.writeFrom(data, 0) + q.endTransfer() + q.enableCache() + return +} + +func (q qspiTransport) writeMemory(addr uint32, data []byte) (err error) { + if (addr + uint32(len(data))) > (qspi_AHB_HI - qspi_AHB_LO) { + return ErrInvalidAddrRange + } + q.disableAndClearCache() + q.runInstruction(cmdQuadPageProgram, iframeWriteMemory) + q.writeFrom(data, addr) + q.endTransfer() + q.enableCache() + return +} + +func (q qspiTransport) eraseCommand(cmd byte, addr uint32) (err error) { + q.disableAndClearCache() + sam.QSPI.INSTRADDR.Set(addr) + q.runInstruction(cmd, iframeEraseCommand) + q.endTransfer() + q.enableCache() + return +} + +func (q qspiTransport) runInstruction(cmd byte, iframe uint32) { + sam.QSPI.INSTRCTRL.Set(uint32(cmd)) + sam.QSPI.INSTRFRAME.Set(iframe) + sam.QSPI.INSTRFRAME.Get() // dummy read for synchronization, as per datasheet +} + +func (q qspiTransport) enableCache() { + sam.CMCC.CTRL.SetBits(sam.CMCC_CTRL_CEN) +} + +func (q qspiTransport) disableAndClearCache() { + sam.CMCC.CTRL.ClearBits(sam.CMCC_CTRL_CEN) + for sam.CMCC.SR.HasBits(sam.CMCC_SR_CSTS) { + } + sam.CMCC.MAINT0.SetBits(sam.CMCC_MAINT0_INVALL) +} + +func (q qspiTransport) endTransfer() { + sam.QSPI.CTRLA.Set(sam.QSPI_CTRLA_ENABLE | sam.QSPI_CTRLA_LASTXFER) + for !sam.QSPI.INTFLAG.HasBits(sam.QSPI_INTFLAG_INSTREND) { + } + sam.QSPI.INTFLAG.Set(sam.QSPI_INTFLAG_INSTREND) +} + +func (q qspiTransport) readInto(buf []byte, addr uint32) { + var ptr = qspi_AHB_LO + uintptr(addr) + for i := range buf { + buf[i] = volatile.LoadUint8((*uint8)(unsafe.Pointer(ptr))) + ptr++ + } + /* // NB(bcg): for some reason this reads data that results from commands in + // a different byte order than the loop above, but works fine for reading + // from memory. Oddly, the above loop seems to work fine in both cases. + ln := len(buf) + sl := (*[1 << 28]byte)(unsafe.Pointer(uintptr(qspi_AHB_LO + addr)))[:ln:ln] + copy(buf, sl) + */ +} + +func (q qspiTransport) writeFrom(buf []byte, addr uint32) { + var ptr = qspi_AHB_LO + uintptr(addr) + for i := range buf { + volatile.StoreUint8((*uint8)(unsafe.Pointer(ptr)), buf[i]) + ptr++ + } +} diff --git a/flash/transport_spi.go b/flash/transport_spi.go new file mode 100644 index 000000000..2da3d5550 --- /dev/null +++ b/flash/transport_spi.go @@ -0,0 +1,154 @@ +package flash + +import "machine" + +type transport interface { + configure(config *DeviceConfig) + supportQuadMode() bool + setClockSpeed(hz uint32) (err error) + runCommand(cmd byte) (err error) + readCommand(cmd byte, rsp []byte) (err error) + writeCommand(cmd byte, data []byte) (err error) + eraseCommand(cmd byte, address uint32) (err error) + readMemory(addr uint32, rsp []byte) (err error) + writeMemory(addr uint32, data []byte) (err error) +} + +// NewSPI returns a pointer to a flash device that uses a SPI peripheral to +// communicate with a serial memory chip. +func NewSPI(spi *machine.SPI, mosi, miso, sck, cs machine.Pin) *Device { + return &Device{ + trans: &spiTransport{ + spi: spi, + mosi: mosi, + miso: miso, + sck: sck, + ss: cs, + }, + } +} + +type spiTransport struct { + spi *machine.SPI + mosi machine.Pin + miso machine.Pin + sck machine.Pin + ss machine.Pin +} + +func (tr *spiTransport) configure(config *DeviceConfig) { + // Configure spi bus + tr.setClockSpeed(5000000) + + // Configure chip select pin + tr.ss.Configure(machine.PinConfig{Mode: machine.PinOutput}) + tr.ss.High() +} + +func (tr *spiTransport) setClockSpeed(hz uint32) error { + // TODO: un-hardcode this max speed; it is probably a sensible + // default maximum for atsamd and nrf at least + if hz > 24*1e6 { + hz = 24 * 1e6 + } + tr.spi.Configure(machine.SPIConfig{ + Frequency: hz, + MISO: tr.miso, + MOSI: tr.mosi, + SCK: tr.sck, + LSBFirst: false, + Mode: 0, + }) + return nil +} + +func (tr *spiTransport) supportQuadMode() bool { + return false +} + +func (tr *spiTransport) runCommand(cmd byte) (err error) { + tr.ss.Low() + _, err = tr.spi.Transfer(byte(cmd)) + tr.ss.High() + return +} + +func (tr *spiTransport) readCommand(cmd byte, rsp []byte) (err error) { + tr.ss.Low() + if _, err := tr.spi.Transfer(byte(cmd)); err == nil { + err = tr.readInto(rsp) + } + tr.ss.High() + return +} + +func (tr *spiTransport) readCommandByte(cmd byte) (rsp byte, err error) { + tr.ss.Low() + if _, err := tr.spi.Transfer(byte(cmd)); err == nil { + rsp, err = tr.spi.Transfer(0xFF) + } + tr.ss.High() + return +} + +func (tr *spiTransport) writeCommand(cmd byte, data []byte) (err error) { + tr.ss.Low() + if _, err := tr.spi.Transfer(byte(cmd)); err == nil { + err = tr.writeFrom(data) + } + tr.ss.High() + return +} + +func (tr *spiTransport) eraseCommand(cmd byte, address uint32) (err error) { + tr.ss.Low() + err = tr.sendAddress(cmd, address) + tr.ss.High() + return +} + +func (tr *spiTransport) readMemory(addr uint32, rsp []byte) (err error) { + tr.ss.Low() + if err = tr.sendAddress(cmdRead, addr); err == nil { + err = tr.readInto(rsp) + } + tr.ss.High() + return +} + +func (tr *spiTransport) writeMemory(addr uint32, data []byte) (err error) { + tr.ss.Low() + if err = tr.sendAddress(cmdPageProgram, addr); err == nil { + err = tr.writeFrom(data) + } + tr.ss.High() + return +} + +func (tr *spiTransport) sendAddress(cmd byte, addr uint32) error { + _, err := tr.spi.Transfer(byte(cmd)) + if err == nil { + _, err = tr.spi.Transfer(byte((addr >> 16) & 0xFF)) + } + if err == nil { + _, err = tr.spi.Transfer(byte((addr >> 8) & 0xFF)) + } + if err == nil { + _, err = tr.spi.Transfer(byte(addr & 0xFF)) + } + return err +} + +func (tr *spiTransport) readInto(rsp []byte) (err error) { + for i, c := 0, len(rsp); i < c && err == nil; i++ { + rsp[i], err = tr.spi.Transfer(0xFF) + } + return +} + +func (tr *spiTransport) writeFrom(data []byte) (err error) { + for i, c := 0, len(data); i < c && err == nil; i++ { + _, err = tr.spi.Transfer(data[i]) + } + return +}