]> sigrok.org Git - libsigrok.git/blobdiff - src/hardware/microchip-pickit2/protocol.c
microchip-pickit2: first driver implementation (acquisition works, triggers don't)
[libsigrok.git] / src / hardware / microchip-pickit2 / protocol.c
index b25f49f5988edb9252920ccf3b4a4a8b0b3b2890..17cc7586ac4baa80255467674ce6c3b443013a0d 100644 (file)
  */
 
 #include <config.h>
+#include <string.h>
 #include "protocol.h"
 
+#define PICKIT2_PACKET_LENGTH  64
+#define PICKIT2_USB_ENDPOINT   1
+#define PICKIT2_USB_TIMEOUT    250
+
+#define PICKIT2_CMD_CHKSTAT    0xa2
+#define PICKIT2_CMD_CHKVOLT    0xa3
+#define PICKIT2_CMD_READ       0xac
+#define PICKIT2_CMD_PADCHAR    0xad
+#define PICKIT2_CMD_SETUP      0xb8
+#define PICKIT2_CMD_SETPOS     0xb9
+
+#define PICKIT2_SEL_BANK0      0x06
+#define PICKIT2_SEL_BANK1      0x07
+
+struct pickit2_cmd {
+       size_t length;
+       uint8_t raw[PICKIT2_PACKET_LENGTH];
+};
+
+static void pickit2_cmd_clear(struct pickit2_cmd *cmd)
+{
+
+       if (!cmd)
+               return;
+       memset(&cmd->raw[0], PICKIT2_CMD_PADCHAR, PICKIT2_PACKET_LENGTH);
+       cmd->length = 0;
+}
+
+static void pickit2_cmd_append(struct pickit2_cmd *cmd, uint8_t b)
+{
+
+       if (!cmd)
+               return;
+       if (cmd->length == PICKIT2_PACKET_LENGTH)
+               return;
+       cmd->raw[cmd->length++] = b;
+}
+
+static int pickit2_usb_send(const struct sr_dev_inst *sdi, struct pickit2_cmd *cmd)
+{
+       struct sr_usb_dev_inst *usb;
+       int ret, sent;
+       GString *text;
+
+       if (!cmd)
+               return SR_OK;
+       usb = sdi->conn;
+       if (!usb)
+               return SR_ERR_ARG;
+
+       text = sr_hexdump_new(&cmd->raw[0], cmd->length);
+       sr_dbg("usb sent: %s", text->str);
+       sr_hexdump_free(text);
+
+       ret = libusb_interrupt_transfer(usb->devhdl,
+               LIBUSB_ENDPOINT_OUT | PICKIT2_USB_ENDPOINT,
+               &cmd->raw[0], PICKIT2_PACKET_LENGTH,
+               &sent, PICKIT2_USB_TIMEOUT);
+       if (ret < 0) {
+               sr_err("USB transmit error: %s.", libusb_error_name(ret));
+               return SR_ERR_IO;
+       }
+       if (sent != PICKIT2_PACKET_LENGTH) {
+               sr_err("USB short send: %d/%d bytes.",
+                       sent, PICKIT2_PACKET_LENGTH);
+               return SR_ERR_IO;
+       }
+
+       return SR_OK;
+}
+
+static int pickit2_usb_recv(const struct sr_dev_inst *sdi, struct pickit2_cmd *cmd)
+{
+       struct sr_usb_dev_inst *usb;
+       int ret, rcvd;
+       GString *text;
+
+       if (!cmd)
+               return SR_ERR_ARG;
+       usb = sdi->conn;
+       if (!usb)
+               return SR_ERR_ARG;
+
+       ret = libusb_interrupt_transfer(usb->devhdl,
+               LIBUSB_ENDPOINT_IN | PICKIT2_USB_ENDPOINT,
+               &cmd->raw[0], PICKIT2_PACKET_LENGTH,
+               &rcvd, PICKIT2_USB_TIMEOUT);
+       if (ret < 0) {
+               if (ret == LIBUSB_ERROR_TIMEOUT)
+                       sr_dbg("USB receive error: %s.", libusb_error_name(ret));
+               else
+                       sr_err("USB receive error: %s.", libusb_error_name(ret));
+               return SR_ERR_IO;
+       }
+
+       text = sr_hexdump_new(&cmd->raw[0], rcvd);
+       sr_dbg("usb recv: %s", text->str);
+       sr_hexdump_free(text);
+
+       cmd->length = rcvd;
+       if (rcvd != PICKIT2_PACKET_LENGTH) {
+               sr_err("USB short recv: %d/%d bytes.",
+                       rcvd, PICKIT2_PACKET_LENGTH);
+               return SR_ERR_IO;
+       }
+
+       return SR_OK;
+}
+
+/* Send a request, (optionally) keep reading until response became available. */
+static int pickit2_usb_send_recv(const struct sr_dev_inst *sdi,
+       struct pickit2_cmd *send_cmd, struct pickit2_cmd *recv_cmd, int do_wait)
+{
+       int ret;
+
+       /* Send the command when one got specified. Ignore errors. */
+       if (send_cmd)
+               (void)pickit2_usb_send(sdi, send_cmd);
+
+       /*
+        * Try receiving data, always ignore errors. When requested by
+        * the caller then keep receiving until response data became
+        * available.
+        */
+       if (!recv_cmd)
+               return SR_OK;
+       do {
+               ret = pickit2_usb_recv(sdi, recv_cmd);
+               if (ret == SR_OK)
+                       return SR_OK;
+               if (!do_wait)
+                       return ret;
+       } while (1);
+       /* UNREACH */
+}
+
+SR_PRIV int microchip_pickit2_setup_trigger(const struct sr_dev_inst *sdi)
+{
+       static const uint8_t trigger_channel_masks[PICKIT2_CHANNEL_COUNT] = {
+               /* Bit positions for channels in trigger registers. */
+               0x04, 0x08, 0x10,
+       };
+       static const uint16_t captureratio_magics[] = {
+               /* TODO
+                * How to exactly calculate these magic 16bit values?
+                * They seem to neither match a percentage value nor a
+                * sample count (assuming 1 window holds 1K samples).
+                * As long as the formula is unknown, we are stuck with
+                * looking up magic values from a table of few pre-sets.
+                */
+               0x0000,                 /* unspecified ratio value */
+               0x03cc, 0x000a, 0x0248, /* 10%/50%/90% in the first window */
+               0x07b4, 0x0b9c, 0x0f84, /* 10% "plus 1/2/3 window widths" */
+       };
+
+       struct dev_context *devc;
+       uint8_t trig_en, trig_lvl, trig_edge, trig_rep, trig_div;
+       uint16_t trig_pos;
+       uint64_t rate;
+       size_t trig_pos_idx, ch_idx;
+       uint8_t ch_mask, ch_cond;
+       struct pickit2_cmd cmd;
+
+       devc = sdi->priv;
+
+       /* Translate user specs to internal setup values. */
+       trig_en = trig_lvl = trig_edge = 0;
+       for (ch_idx = 0; ch_idx < PICKIT2_CHANNEL_COUNT; ch_idx++) {
+               if (!devc->triggers[ch_idx])
+                       continue;
+               ch_mask = trigger_channel_masks[ch_idx];
+               ch_cond = devc->triggers[ch_idx];
+               trig_en |= ch_mask;
+               switch (ch_cond) {
+               case SR_TRIGGER_ONE:
+               case SR_TRIGGER_RISING:
+                       trig_lvl |= ch_mask;
+                       break;
+               }
+               switch (ch_cond) {
+               case SR_TRIGGER_FALLING:
+               case SR_TRIGGER_RISING:
+                       trig_edge |= ch_mask;
+                       break;
+               }
+       }
+       trig_rep = 1;
+       trig_rep = MIN(trig_rep, 255);
+       trig_rep = MAX(trig_rep, 1);
+       if (!trig_en)
+               trig_rep = 0;
+       rate = devc->samplerates[devc->curr_samplerate_idx];
+       rate = SR_MHZ(1) / rate - 1;
+       trig_div = rate & 0xff;
+       trig_pos_idx = devc->trigpos;
+       if (trig_pos_idx >= ARRAY_SIZE(captureratio_magics))
+               trig_pos_idx = 0;
+       trig_pos = captureratio_magics[trig_pos_idx];
+
+       /* Construct the SETUP packet. */
+       pickit2_cmd_clear(&cmd);
+       pickit2_cmd_append(&cmd, PICKIT2_CMD_SETUP);
+       pickit2_cmd_append(&cmd, 0x01);
+       pickit2_cmd_append(&cmd, trig_en);
+       pickit2_cmd_append(&cmd, trig_lvl);
+       pickit2_cmd_append(&cmd, trig_edge);
+       pickit2_cmd_append(&cmd, trig_rep);
+       pickit2_cmd_append(&cmd, trig_pos % 256);
+       pickit2_cmd_append(&cmd, trig_pos / 256);
+       pickit2_cmd_append(&cmd, trig_div);
+
+       /*
+        * Transmit the SETUP packet. Only send it out, poll for the
+        * response later. When a trigger is involved, the response may
+        * take considerable amounts of time to arrive. We want apps
+        * to remain responsive during that period of time.
+        */
+       (void)pickit2_usb_send_recv(sdi, &cmd, NULL, FALSE);
+
+       return SR_OK;
+}
+
+/* Read specified bank data at given offset into caller provided buffer. */
+static int pickit2_retrieve_bank(struct sr_dev_inst *sdi,
+       size_t bank_idx, size_t offset, uint8_t **buf, size_t *len)
+{
+       struct pickit2_cmd send_cmd, recv_cmd;
+       int ret;
+       size_t copy_iter, copy_len;
+
+       /* Construct and send the SETPOS packet. No response expected. */
+       pickit2_cmd_clear(&send_cmd);
+       pickit2_cmd_append(&send_cmd, PICKIT2_CMD_SETPOS);
+       pickit2_cmd_append(&send_cmd, offset & 0xff);
+       pickit2_cmd_append(&send_cmd, PICKIT2_SEL_BANK0 + bank_idx);
+       ret = pickit2_usb_send_recv(sdi, &send_cmd, NULL, FALSE);
+       if (ret != SR_OK)
+               return ret;
+       sr_dbg("read bank: pos set");
+
+       /* Run two READ cycles, get 2x 64 bytes => 128 bytes raw data. */
+       pickit2_cmd_clear(&send_cmd);
+       pickit2_cmd_append(&send_cmd, PICKIT2_CMD_READ);
+       copy_iter = 2;
+       while (copy_iter-- > 0) {
+               ret = pickit2_usb_send_recv(sdi, &send_cmd, &recv_cmd, TRUE);
+               if (ret != SR_OK)
+                       return ret;
+               copy_len = MIN(PICKIT2_PACKET_LENGTH, *len);
+               memcpy(*buf, &recv_cmd.raw[0], copy_len);
+               *buf += copy_len;
+               *len -= copy_len;
+       }
+
+       return SR_OK;
+}
+
+/* Read all of the (banked, raw) sample data after acquisition completed. */
+static int pickit2_retrieve_sample_data(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       uint8_t *rdpos;
+       size_t rdlen;
+       int ret;
+
+       devc = sdi->priv;
+       rdpos = &devc->samples_raw[0];
+       rdlen = sizeof(devc->samples_raw);
+
+       ret = pickit2_retrieve_bank(sdi, 0, 0x00, &rdpos, &rdlen);
+       if (ret)
+               return ret;
+       ret = pickit2_retrieve_bank(sdi, 0, 0x80, &rdpos, &rdlen);
+       if (ret)
+               return ret;
+       ret = pickit2_retrieve_bank(sdi, 1, 0x00, &rdpos, &rdlen);
+       if (ret)
+               return ret;
+       ret = pickit2_retrieve_bank(sdi, 1, 0x80, &rdpos, &rdlen);
+       if (ret)
+               return ret;
+
+       return SR_OK;
+}
+
+/* Send converted sample data to the sigrok session. */
+static int pickit2_submit_logic_data(struct sr_dev_inst *sdi)
+{
+       struct dev_context *devc;
+       struct {
+               uint8_t raw_mask, conv_mask;
+       } ch_map[PICKIT2_CHANNEL_COUNT] = {
+               { 0x04, 0x01, },
+               { 0x08, 0x02, },
+               { 0x01, 0x04, },
+       };
+       uint8_t *raw_buf, raw_byte, *conv_buf;
+       size_t raw_len, conv_len;
+       uint64_t limit;
+       struct sr_datafeed_packet packet;
+       struct sr_datafeed_logic logic;
+
+       devc = sdi->priv;
+
+       /*
+        * TODO Manipulate (or create) the above channel mapping table.
+        * Remove disabled channels, create dense output format.
+        * Could:
+        * - Loop over the index, check the corresponding channel's
+        *   state, clear out the conv_mask part and shift down all
+        *   subsequent conv_mask parts.
+        */
+
+       /*
+        * Convert raw dump (two samples per byte, at odd positions) to
+        * internal sigrok format (one sample per byte, at increasing
+        * offsets which start at 0).
+        */
+#define handle_nibble(n) do { \
+       uint8_t conv_byte; \
+       size_t ch_idx; \
+       conv_byte = 0x00; \
+       for (ch_idx = 0; ch_idx < PICKIT2_CHANNEL_COUNT; ch_idx++) { \
+               if ((n) & ch_map[ch_idx].raw_mask) \
+                       conv_byte |= ch_map[ch_idx].conv_mask; \
+       } \
+       *conv_buf++ = conv_byte; \
+       conv_len++; \
+} while (0)
+
+       raw_len = sizeof(devc->samples_raw);
+       raw_buf = &devc->samples_raw[raw_len];
+       conv_buf = &devc->samples_conv[0];
+       conv_len = 0;
+       while (raw_len-- > 0) {
+               raw_byte = *(--raw_buf);
+               handle_nibble((raw_byte >> 0) & 0x0f);
+               handle_nibble((raw_byte >> 4) & 0x0f);
+       }
+
+       /* Submit a logic packet to the sigrok session. */
+       packet.type = SR_DF_LOGIC;
+       packet.payload = &logic;
+       logic.unitsize = sizeof(uint8_t);
+       logic.data = &devc->samples_conv[0];
+       logic.length = conv_len;
+       limit = devc->sw_limits.limit_samples;
+       if (limit && limit < logic.length)
+               logic.length = limit;
+       sr_session_send(sdi, &packet);
+
+       return SR_OK;
+}
+
+static gboolean pickit2_status_is_cancel(uint16_t status)
+{
+       /* "Button press" and "transfer timeout" translate to "cancelled". */
+       static const uint16_t status_cancel_mask = 0x4004;
+
+       sr_dbg("recv: status 0x%04x", status);
+       if ((status & status_cancel_mask) == status_cancel_mask)
+               return TRUE;
+       return FALSE;
+}
+
+/* Periodically invoked poll routine, checking for incoming receive data. */
 SR_PRIV int microchip_pickit2_receive_data(int fd, int revents, void *cb_data)
 {
-       const struct sr_dev_inst *sdi;
+       struct sr_dev_inst *sdi;
        struct dev_context *devc;
+       struct pickit2_cmd cmd;
+       int ret;
+       uint16_t status;
 
        (void)fd;
+       (void)revents;
 
-       if (!(sdi = cb_data))
+       sdi = cb_data;
+       if (!sdi)
                return TRUE;
-
-       if (!(devc = sdi->priv))
+       devc = sdi->priv;
+       if (!devc)
                return TRUE;
 
-       if (revents == G_IO_IN) {
-               /* TODO */
+       /* Waiting for the trigger condition? */
+       if (devc->state == STATE_WAIT) {
+               /* Keep waiting until status becomes available. */
+               ret = pickit2_usb_send_recv(sdi, NULL, &cmd, FALSE);
+               if (ret != SR_OK)
+                       return TRUE;
+               /* Check status flags for cancel requests. */
+               devc->state = STATE_DATA;
+               status = RL16(&cmd.raw[0]);
+               if (pickit2_status_is_cancel(status)) {
+                       sr_info("User cancelled acquisition.");
+                       sr_dev_acquisition_stop(sdi);
+                       return TRUE;
+               }
+               sr_dbg("recv: Data has become available.");
+               /* FALLTHROUGH */
        }
 
+       /*
+        * Retrieve acquired sample data (blocking, acquisition has
+        * completed and samples are few), and stop acquisition (have
+        * the poll routine unregistered).
+        */
+       ret = pickit2_retrieve_sample_data(sdi);
+       if (ret != SR_OK)
+               return ret;
+       ret = pickit2_submit_logic_data(sdi);
+       if (ret != SR_OK)
+               return ret;
+       sr_dev_acquisition_stop(sdi);
        return TRUE;
 }