mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
331a60ce2d
Signed-off-by: deadprogram <ron@hybridgroup.com>
648 lines
12 KiB
Go
648 lines
12 KiB
Go
// package sdcard provides a TinyGo driver for sdcard/mmc devices
|
|
// using a SPI connection.
|
|
//
|
|
// To use a file system on the SDcard, please see the TinyFS repo:
|
|
//
|
|
// https://github.com/tinygo-org/tinyfs
|
|
package sdcard
|
|
|
|
import (
|
|
"fmt"
|
|
"machine"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
_CMD_TIMEOUT = 100
|
|
|
|
_R1_IDLE_STATE = 1 << 0
|
|
_R1_ERASE_RESET = 1 << 1
|
|
_R1_ILLEGAL_COMMAND = 1 << 2
|
|
_R1_COM_CRC_ERROR = 1 << 3
|
|
_R1_ERASE_SEQUENCE_ERROR = 1 << 4
|
|
_R1_ADDRESS_ERROR = 1 << 5
|
|
_R1_PARAMETER_ERROR = 1 << 6
|
|
|
|
// card types
|
|
SD_CARD_TYPE_SD1 = 1 // Standard capacity V1 SD card
|
|
SD_CARD_TYPE_SD2 = 2 // Standard capacity V2 SD card
|
|
SD_CARD_TYPE_SDHC = 3 // High Capacity SD card
|
|
)
|
|
|
|
var (
|
|
dummy [512]byte
|
|
)
|
|
|
|
type Device struct {
|
|
bus *machine.SPI
|
|
sck machine.Pin
|
|
sdo machine.Pin
|
|
sdi machine.Pin
|
|
cs machine.Pin
|
|
cmdbuf []byte
|
|
dummybuf []byte
|
|
tokenbuf []byte
|
|
sdCardType byte
|
|
CID *CID
|
|
CSD *CSD
|
|
}
|
|
|
|
func New(b *machine.SPI, sck, sdo, sdi, cs machine.Pin) Device {
|
|
return Device{
|
|
bus: b,
|
|
cs: cs,
|
|
sck: sck,
|
|
sdo: sdo,
|
|
sdi: sdi,
|
|
cmdbuf: make([]byte, 6),
|
|
dummybuf: make([]byte, 512),
|
|
tokenbuf: make([]byte, 1),
|
|
sdCardType: 0,
|
|
}
|
|
}
|
|
|
|
func (d *Device) Configure() error {
|
|
return d.initCard()
|
|
}
|
|
|
|
func (d *Device) initCard() error {
|
|
d.bus.Configure(machine.SPIConfig{
|
|
SCK: d.sck,
|
|
SDO: d.sdo,
|
|
SDI: d.sdi,
|
|
Frequency: 250000,
|
|
LSBFirst: false,
|
|
Mode: 0, // phase=0, polarity=0
|
|
})
|
|
|
|
// set pin modes
|
|
d.cs.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
|
d.cs.High()
|
|
|
|
for i := range dummy {
|
|
dummy[i] = 0xFF
|
|
}
|
|
|
|
// clock card at least 100 cycles with cs high
|
|
d.bus.Tx(dummy[:10], nil)
|
|
|
|
d.cs.Low()
|
|
d.bus.Tx(dummy[:], nil)
|
|
|
|
// CMD0: init card; sould return _R1_IDLE_STATE (allow 5 attempts)
|
|
ok := false
|
|
tm := setTimeout(0, 2*time.Second)
|
|
for !tm.expired() {
|
|
// Wait up to 2 seconds to be the same as the Arduino
|
|
if d.cmd(CMD0_GO_IDLE_STATE, 0, 0x95) == _R1_IDLE_STATE {
|
|
ok = true
|
|
break
|
|
}
|
|
}
|
|
if !ok {
|
|
return fmt.Errorf("no SD card")
|
|
}
|
|
|
|
// CMD8: determine card version
|
|
r := d.cmd(CMD8_SEND_IF_COND, 0x01AA, 0x87)
|
|
if (r & _R1_ILLEGAL_COMMAND) == _R1_ILLEGAL_COMMAND {
|
|
d.sdCardType = SD_CARD_TYPE_SD1
|
|
return fmt.Errorf("init_card_v1 not impl\r\n")
|
|
} else {
|
|
// r7 response
|
|
status := byte(0)
|
|
for i := 0; i < 3; i++ {
|
|
var err error
|
|
status, err = d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if (status & 0x0F) != 0x01 {
|
|
return fmt.Errorf("SD_CARD_ERROR_CMD8 %02X", status)
|
|
}
|
|
|
|
for i := 3; i < 4; i++ {
|
|
var err error
|
|
status, err = d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if status != 0xAA {
|
|
return fmt.Errorf("SD_CARD_ERROR_CMD8 %02X", status)
|
|
}
|
|
d.sdCardType = SD_CARD_TYPE_SD2
|
|
}
|
|
|
|
// initialize card and send host supports SDHC if SD2
|
|
arg := uint32(0)
|
|
if d.sdCardType == SD_CARD_TYPE_SD2 {
|
|
arg = 0x40000000
|
|
}
|
|
|
|
// check for timeout
|
|
ok = false
|
|
tm = setTimeout(0, 2*time.Second)
|
|
for !tm.expired() {
|
|
if d.acmd(ACMD41_SD_APP_OP_COND, arg) == 0 {
|
|
ok = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !ok {
|
|
return fmt.Errorf("SD_CARD_ERROR_ACMD41")
|
|
}
|
|
|
|
// if SD2 read OCR register to check for SDHC card
|
|
if d.sdCardType == SD_CARD_TYPE_SD2 {
|
|
if d.cmd(CMD58_READ_OCR, 0, 0xFF) != 0 {
|
|
return fmt.Errorf("SD_CARD_ERROR_CMD58")
|
|
}
|
|
|
|
status, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (status & 0xC0) == 0xC0 {
|
|
d.sdCardType = SD_CARD_TYPE_SDHC
|
|
}
|
|
// discard rest of ocr - contains allowed voltage range
|
|
for i := 1; i < 4; i++ {
|
|
d.bus.Transfer(byte(0xFF))
|
|
}
|
|
}
|
|
|
|
if d.cmd(CMD16_SET_BLOCKLEN, 0x0200, 0xFF) != 0 {
|
|
return fmt.Errorf("SD_CARD_ERROR_CMD16")
|
|
}
|
|
|
|
var buf [16]byte
|
|
// read CID
|
|
err := d.ReadCID(buf[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.CID = NewCID(buf[:])
|
|
|
|
// read CSD
|
|
err = d.ReadCSD(buf[:])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
d.CSD = NewCSD(buf[:])
|
|
|
|
d.cs.High()
|
|
|
|
d.bus.Configure(machine.SPIConfig{
|
|
SCK: d.sck,
|
|
SDO: d.sdo,
|
|
SDI: d.sdi,
|
|
Frequency: 4000000,
|
|
LSBFirst: false,
|
|
Mode: 0, // phase=0, polarity=0
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d Device) acmd(cmd byte, arg uint32) byte {
|
|
d.cmd(CMD55_APP_CMD, 0, 0xFF)
|
|
return d.cmd(cmd, arg, 0xFF)
|
|
}
|
|
|
|
func (d Device) cmd(cmd byte, arg uint32, crc byte) byte {
|
|
d.cs.Low()
|
|
|
|
if cmd != 12 {
|
|
d.waitNotBusy(300 * time.Millisecond)
|
|
}
|
|
|
|
// create and send the command
|
|
buf := d.cmdbuf
|
|
buf[0] = 0x40 | cmd
|
|
buf[1] = byte(arg >> 24)
|
|
buf[2] = byte(arg >> 16)
|
|
buf[3] = byte(arg >> 8)
|
|
buf[4] = byte(arg)
|
|
buf[5] = crc
|
|
d.bus.Tx(buf, nil)
|
|
|
|
if cmd == 12 {
|
|
// skip 1 byte
|
|
d.bus.Transfer(byte(0xFF))
|
|
}
|
|
|
|
// wait for the response (response[7] == 0)
|
|
for i := 0; i < 0xFFFF; i++ {
|
|
d.bus.Tx([]byte{0xFF}, d.tokenbuf)
|
|
response := d.tokenbuf[0]
|
|
if (response & 0x80) == 0 {
|
|
return response
|
|
}
|
|
}
|
|
|
|
// TODO
|
|
//// timeout
|
|
d.cs.High()
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
return 0xFF // -1
|
|
}
|
|
|
|
func (d Device) waitNotBusy(timeout time.Duration) error {
|
|
tm := setTimeout(1, timeout)
|
|
for !tm.expired() {
|
|
r, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if r == 0xFF {
|
|
return nil
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (d Device) waitStartBlock() error {
|
|
status := byte(0xFF)
|
|
|
|
tm := setTimeout(0, 300*time.Millisecond)
|
|
for !tm.expired() {
|
|
var err error
|
|
status, err = d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
d.cs.High()
|
|
return err
|
|
}
|
|
if status != 0xFF {
|
|
break
|
|
}
|
|
}
|
|
|
|
if status != 254 {
|
|
d.cs.High()
|
|
return fmt.Errorf("SD_CARD_START_BLOCK")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadCSD reads the CSD using CMD9.
|
|
func (d Device) ReadCSD(csd []byte) error {
|
|
return d.readRegister(CMD9_SEND_CSD, csd)
|
|
}
|
|
|
|
// ReadCID reads the CID using CMD10
|
|
func (d Device) ReadCID(csd []byte) error {
|
|
return d.readRegister(CMD10_SEND_CID, csd)
|
|
}
|
|
|
|
func (d Device) readRegister(cmd uint8, dst []byte) error {
|
|
if d.cmd(cmd, 0, 0xFF) != 0 {
|
|
return fmt.Errorf("SD_CARD_ERROR_READ_REG")
|
|
}
|
|
if err := d.waitStartBlock(); err != nil {
|
|
return err
|
|
}
|
|
// transfer data
|
|
for i := uint16(0); i < 16; i++ {
|
|
r, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dst[i] = r
|
|
}
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.cs.High()
|
|
|
|
return nil
|
|
}
|
|
|
|
// ReadData reads 512 bytes from sdcard into dst.
|
|
func (d Device) ReadData(block uint32, dst []byte) error {
|
|
if len(dst) < 512 {
|
|
return fmt.Errorf("len(dst) must be greater than or equal to 512")
|
|
}
|
|
|
|
// use address if not SDHC card
|
|
if d.sdCardType != SD_CARD_TYPE_SDHC {
|
|
block <<= 9
|
|
}
|
|
if d.cmd(CMD17_READ_SINGLE_BLOCK, block, 0xFF) != 0 {
|
|
return fmt.Errorf("CMD17 error")
|
|
}
|
|
if err := d.waitStartBlock(); err != nil {
|
|
return fmt.Errorf("waitStartBlock()")
|
|
}
|
|
|
|
err := d.bus.Tx(dummy[:512], dst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// skip CRC (2byte)
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
// TODO: probably not necessary
|
|
d.cs.High()
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteMultiStart starts the continuous write mode using CMD25.
|
|
func (d Device) WriteMultiStart(block uint32) error {
|
|
// use address if not SDHC card
|
|
if d.sdCardType != SD_CARD_TYPE_SDHC {
|
|
block <<= 9
|
|
}
|
|
if d.cmd(CMD25_WRITE_MULTIPLE_BLOCK, block, 0xFF) != 0 {
|
|
return fmt.Errorf("CMD25 error")
|
|
}
|
|
|
|
// skip 1 byte
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteMulti performs continuous writing. It is necessary to call
|
|
// WriteMultiStart() in prior.
|
|
func (d Device) WriteMulti(buf []byte) error {
|
|
// send Data Token for CMD25
|
|
d.bus.Transfer(byte(0xFC))
|
|
|
|
for i := 0; i < 512; i++ {
|
|
_, err := d.bus.Transfer(buf[i])
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// send dummy CRC (2 byte)
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
// Data Resp.
|
|
r, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (r & 0x1F) != 0x05 {
|
|
return fmt.Errorf("SD_CARD_ERROR_WRITE")
|
|
}
|
|
|
|
// wait no busy
|
|
err = d.waitNotBusy(600 * time.Millisecond)
|
|
if err != nil {
|
|
return fmt.Errorf("SD_CARD_ERROR_WRITE_TIMEOUT")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteMultiStop exits the continuous write mode.
|
|
func (d Device) WriteMultiStop() error {
|
|
defer d.cs.High()
|
|
|
|
// Stop Tran token for CMD25
|
|
d.bus.Transfer(0xFD)
|
|
|
|
// skip 1 byte
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
err := d.waitNotBusy(600 * time.Millisecond)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteData writes 512 bytes from dst to sdcard.
|
|
func (d Device) WriteData(block uint32, src []byte) error {
|
|
if len(src) < 512 {
|
|
return fmt.Errorf("len(src) must be greater than or equal to 512")
|
|
}
|
|
|
|
// use address if not SDHC card
|
|
if d.sdCardType != SD_CARD_TYPE_SDHC {
|
|
block <<= 9
|
|
}
|
|
if d.cmd(CMD24_WRITE_BLOCK, block, 0xFF) != 0 {
|
|
return fmt.Errorf("CMD24 error")
|
|
}
|
|
|
|
// wait 1 byte?
|
|
token := byte(0xFE)
|
|
d.bus.Transfer(token)
|
|
|
|
err := d.bus.Tx(src[:512], nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// send dummy CRC (2 byte)
|
|
d.bus.Transfer(byte(0xFF))
|
|
d.bus.Transfer(byte(0xFF))
|
|
|
|
// Data Resp.
|
|
r, err := d.bus.Transfer(byte(0xFF))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if (r & 0x1F) != 0x05 {
|
|
return fmt.Errorf("SD_CARD_ERROR_WRITE")
|
|
}
|
|
|
|
// wait no busy
|
|
err = d.waitNotBusy(600 * time.Millisecond)
|
|
if err != nil {
|
|
return fmt.Errorf("SD_CARD_ERROR_WRITE_TIMEOUT")
|
|
}
|
|
|
|
// TODO: probably not necessary
|
|
d.cs.High()
|
|
return nil
|
|
}
|
|
|
|
// ReadAt reads the given number of bytes from the sdcard.
|
|
func (dev *Device) ReadAt(buf []byte, addr int64) (int, error) {
|
|
block := uint64(addr)
|
|
// use address if not SDHC card
|
|
if dev.sdCardType == SD_CARD_TYPE_SDHC {
|
|
block >>= 9
|
|
}
|
|
|
|
idx := uint32(0)
|
|
|
|
start := uint32(addr % 512)
|
|
end := uint32(0)
|
|
remain := uint32(len(buf))
|
|
|
|
// If data starts in the middle
|
|
if 0 < start {
|
|
if start+remain <= 512 {
|
|
end = start + remain
|
|
} else {
|
|
end = 512
|
|
}
|
|
|
|
err := dev.ReadData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
copy(buf[idx:], dev.dummybuf[start:end])
|
|
|
|
remain -= end - start
|
|
idx += end - start
|
|
block++
|
|
}
|
|
|
|
// If more than 512 bytes left
|
|
for 512 <= remain {
|
|
start = 0
|
|
end = 512
|
|
|
|
err := dev.ReadData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
copy(buf[idx:], dev.dummybuf[start:end])
|
|
|
|
remain -= end - start
|
|
idx += end - start
|
|
block++
|
|
}
|
|
|
|
// Read to the end
|
|
if 0 < remain {
|
|
start = 0
|
|
end = remain
|
|
|
|
err := dev.ReadData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
copy(buf[idx:], dev.dummybuf[start:end])
|
|
|
|
remain -= end - start
|
|
idx += end - start
|
|
block++
|
|
}
|
|
|
|
return int(idx), nil
|
|
}
|
|
|
|
// WriteAt writes the given number of bytes to sdcard.
|
|
func (dev *Device) WriteAt(buf []byte, addr int64) (n int, err error) {
|
|
block := uint64(addr)
|
|
// use address if not SDHC card
|
|
if dev.sdCardType == SD_CARD_TYPE_SDHC {
|
|
block >>= 9
|
|
}
|
|
|
|
idx := uint32(0)
|
|
|
|
start := uint32(addr % 512)
|
|
end := uint32(0)
|
|
remain := uint32(len(buf))
|
|
|
|
// If data starts in the middle
|
|
if 0 < start {
|
|
if start+remain <= 512 {
|
|
end = start + remain
|
|
} else {
|
|
end = 512
|
|
}
|
|
|
|
err := dev.ReadData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
copy(dev.dummybuf[start:end], buf[idx:])
|
|
|
|
err = dev.WriteData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
remain -= end - start
|
|
idx += end - start
|
|
block++
|
|
}
|
|
|
|
// If more than 512 bytes left
|
|
for 512 <= remain {
|
|
start = 0
|
|
end = 512
|
|
|
|
err := dev.WriteData(uint32(block), buf[idx:idx+512])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
remain -= end - start
|
|
idx += end - start
|
|
block++
|
|
}
|
|
|
|
// Write to the end
|
|
if 0 < remain {
|
|
start = 0
|
|
end = remain
|
|
|
|
err := dev.ReadData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
copy(dev.dummybuf[start:end], buf[idx:])
|
|
|
|
err = dev.WriteData(uint32(block), dev.dummybuf)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
remain -= end - start
|
|
idx += end - start
|
|
block++
|
|
}
|
|
|
|
return int(idx), nil
|
|
}
|
|
|
|
// Size returns the number of bytes in this sdcard.
|
|
func (dev *Device) Size() int64 {
|
|
return int64(dev.CSD.Size())
|
|
}
|
|
|
|
// WriteBlockSize returns the block size in which data can be written to
|
|
// memory.
|
|
func (dev *Device) WriteBlockSize() int64 {
|
|
return 512
|
|
}
|
|
|
|
// EraseBlockSize returns the smallest erasable area on this sdcard in bytes.
|
|
func (dev *Device) EraseBlockSize() int64 {
|
|
return 512
|
|
}
|
|
|
|
// EraseBlocks erases the given number of blocks.
|
|
func (dev *Device) EraseBlocks(start, len int64) error {
|
|
dev.WriteMultiStart(uint32(start))
|
|
|
|
for i := range dev.dummybuf {
|
|
dev.dummybuf[i] = 0
|
|
}
|
|
|
|
for i := 0; i < int(len); i++ {
|
|
dev.WriteMulti(dev.dummybuf)
|
|
}
|
|
|
|
dev.WriteMultiStop()
|
|
return nil
|
|
}
|