/* * This file is part of the i2c-star project. * * Copyright (C) 2014 Daniel Thompson * * This library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const struct usb_device_descriptor dev = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, .bcdUSB = 0x0200, .bDeviceClass = 0xff, .bDeviceSubClass = 0, .bDeviceProtocol = 0, .bMaxPacketSize0 = 64, .idVendor = 0x04b4, .idProduct = 0xfb11, .bcdDevice = 0x0205, .iManufacturer = 1, .iProduct = 2, .iSerialNumber = 0, .bNumConfigurations = 1, }; const struct usb_endpoint_descriptor i2c_endpoint = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = 0x81, .bmAttributes = USB_ENDPOINT_ATTR_INTERRUPT, .wMaxPacketSize = 4, .bInterval = 0x20, }; const struct usb_interface_descriptor i2c_iface = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 1, .bInterfaceClass = 0, .bInterfaceSubClass = 0, .bInterfaceProtocol = 0, .iInterface = 0, .endpoint = &i2c_endpoint, }; const struct usb_interface ifaces[] = {{ .num_altsetting = 1, .altsetting = &i2c_iface, }}; static const struct usb_config_descriptor config = { .bLength = USB_DT_CONFIGURATION_SIZE, .bDescriptorType = USB_DT_CONFIGURATION, .wTotalLength = 0, /* ?automatically calculated? */ .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 0, .bmAttributes = 0x80, /* bus powered */ .bMaxPower = 0x32, .interface = ifaces, }; static const char *usb_strings[] = { "redfelineninja.org.uk", "i2c-stm32f1-usb", }; /* Buffer to be used for control requests. */ uint8_t usbd_control_buffer[128]; /* commands from USB, must e.g. match command ids in kernel driver */ #define CMD_ECHO 0 #define CMD_GET_FUNC 1 #define CMD_SET_DELAY 2 #define CMD_GET_STATUS 3 #define CMD_I2C_IO 4 #define CMD_I2C_BEGIN 1 // flag fo I2C_IO #define CMD_I2C_END 2 // flag fo I2C_IO /* linux kernel flags */ #define I2C_M_TEN 0x10 /* we have a ten bit chip address */ #define I2C_M_RD 0x01 #define I2C_M_NOSTART 0x4000 #define I2C_M_REV_DIR_ADDR 0x2000 #define I2C_M_IGNORE_NAK 0x1000 #define I2C_M_NO_RD_ACK 0x0800 /* To determine what functionality is present */ #define I2C_FUNC_I2C 0x00000001 #define I2C_FUNC_10BIT_ADDR 0x00000002 #define I2C_FUNC_PROTOCOL_MANGLING 0x00000004 /* I2C_M_{REV_DIR_ADDR,NOSTART,..} */ #define I2C_FUNC_SMBUS_HWPEC_CALC 0x00000008 /* SMBus 2.0 */ #define I2C_FUNC_NOSTART 0x00000010 /* I2C_M_NOSTART */ #define I2C_FUNC_SMBUS_READ_WORD_DATA_PEC 0x00000800 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_WRITE_WORD_DATA_PEC 0x00001000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_PROC_CALL_PEC 0x00002000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BLOCK_PROC_CALL_PEC 0x00004000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BLOCK_PROC_CALL 0x00008000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_QUICK 0x00010000 #define I2C_FUNC_SMBUS_READ_BYTE 0x00020000 #define I2C_FUNC_SMBUS_WRITE_BYTE 0x00040000 #define I2C_FUNC_SMBUS_READ_BYTE_DATA 0x00080000 #define I2C_FUNC_SMBUS_WRITE_BYTE_DATA 0x00100000 #define I2C_FUNC_SMBUS_READ_WORD_DATA 0x00200000 #define I2C_FUNC_SMBUS_WRITE_WORD_DATA 0x00400000 #define I2C_FUNC_SMBUS_PROC_CALL 0x00800000 #define I2C_FUNC_SMBUS_READ_BLOCK_DATA 0x01000000 #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA 0x02000000 #define I2C_FUNC_SMBUS_READ_I2C_BLOCK 0x04000000 /* I2C-like block xfer */ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK 0x08000000 /* w/ 1-byte reg. addr. */ #define I2C_FUNC_SMBUS_READ_I2C_BLOCK_2 0x10000000 /* I2C-like block xfer */ #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK_2 0x20000000 /* w/ 2-byte reg. addr. */ #define I2C_FUNC_SMBUS_READ_BLOCK_DATA_PEC 0x40000000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC 0x80000000 /* SMBus 2.0 */ #define I2C_FUNC_SMBUS_BYTE I2C_FUNC_SMBUS_READ_BYTE | \ I2C_FUNC_SMBUS_WRITE_BYTE #define I2C_FUNC_SMBUS_BYTE_DATA I2C_FUNC_SMBUS_READ_BYTE_DATA | \ I2C_FUNC_SMBUS_WRITE_BYTE_DATA #define I2C_FUNC_SMBUS_WORD_DATA I2C_FUNC_SMBUS_READ_WORD_DATA | \ I2C_FUNC_SMBUS_WRITE_WORD_DATA #define I2C_FUNC_SMBUS_BLOCK_DATA I2C_FUNC_SMBUS_READ_BLOCK_DATA | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA #define I2C_FUNC_SMBUS_I2C_BLOCK I2C_FUNC_SMBUS_READ_I2C_BLOCK | \ I2C_FUNC_SMBUS_WRITE_I2C_BLOCK #define I2C_FUNC_SMBUS_EMUL I2C_FUNC_SMBUS_QUICK | \ I2C_FUNC_SMBUS_BYTE | \ I2C_FUNC_SMBUS_BYTE_DATA | \ I2C_FUNC_SMBUS_WORD_DATA | \ I2C_FUNC_SMBUS_PROC_CALL | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA | \ I2C_FUNC_SMBUS_WRITE_BLOCK_DATA_PEC | \ I2C_FUNC_SMBUS_I2C_BLOCK /* the currently support capability is quite limited */ const unsigned long func = I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_NOSTART; #define STATUS_IDLE 0 #define STATUS_ADDRESS_ACK 1 #define STATUS_ADDRESS_NACK 2 static uint8_t status = STATUS_IDLE; uint32_t i2c = I2C1; /*! * \brief Handle I2C I/O request. * * \todo There is no bus error checking at all... */ static int usb_i2c_io(struct usb_setup_data *req, uint8_t *buf, uint16_t *len) { uint32_t reg32 __attribute__((unused)); /* Interpret the request */ uint8_t cmd = req->bRequest; uint8_t address = req->wIndex; uint8_t is_read = req->wValue & I2C_M_RD; uint8_t size = req->wLength; i2c_ctx_t ctx; i2c_ctx_init(&ctx, I2C1); if (!(req->wValue & I2C_M_NOSTART)) { /* We can ignore CMD_I2C_BEGIN, the hardware will work out which * type of start condition to generate. */ PT_CALL(&ctx.leaf, i2c_ctx_start(&ctx)); if (ctx.err) goto err; /* Send the address */ PT_CALL(&ctx.leaf, i2c_ctx_sendaddr(&ctx, address, (is_read ? size : 0))); if (ctx.err) goto err; } /* Perform the transaction */ for (int i=0; ibRequest) { case CMD_ECHO: memcpy(reply_buf, &req->wValue, sizeof(req->wValue)); *buf = reply_buf; *len = sizeof(req->wValue); return USBD_REQ_HANDLED; case CMD_GET_FUNC: /* Report our capabilities */ memcpy(reply_buf, &func, sizeof(func)); *buf = reply_buf; *len = sizeof(func); return USBD_REQ_HANDLED; case CMD_SET_DELAY: /* This was used in i2c-tiny-usb to choose the clock * frequency by specifying the shortest time between * clock edges. * * This implementation silently ignores delay requests. We * run the hardware as fast as we are permitted. */ *buf = reply_buf; *len = 0; return USBD_REQ_HANDLED; case CMD_I2C_IO: case CMD_I2C_IO | CMD_I2C_BEGIN: case CMD_I2C_IO | CMD_I2C_END: case CMD_I2C_IO | CMD_I2C_BEGIN | CMD_I2C_END: if (req->wValue & I2C_M_RD) *buf = reply_buf; return usb_i2c_io(req, *buf, len); break; case CMD_GET_STATUS: memcpy(reply_buf, &status, sizeof(status)); *buf = reply_buf; *len = sizeof(status); return USBD_REQ_HANDLED; default: break; } return USBD_REQ_NEXT_CALLBACK; } static void usb_set_config(usbd_device *usbd_dev, uint16_t wValue) { (void)wValue; usbd_ep_setup(usbd_dev, 0x81, USB_ENDPOINT_ATTR_INTERRUPT, 4, NULL); usbd_register_control_callback( usbd_dev, USB_REQ_TYPE_VENDOR | USB_REQ_TYPE_INTERFACE, USB_REQ_TYPE_TYPE | USB_REQ_TYPE_RECIPIENT, usb_control_request); } static usbd_device *usbd_dev; static int usb_fibre(fibre_t *fibre) { static uint32_t t; PT_BEGIN_FIBRE(fibre); rcc_periph_clock_enable(RCC_GPIOA); /* lower hotplug and leave enough time for the host to notice */ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO11 | GPIO12); gpio_clear(GPIOA, GPIO11 | GPIO12); t = time_now() + 10000; PT_WAIT_UNTIL(fibre_timeout(t)); usbd_dev = usbd_init(&st_usbfs_v1_usb_driver, &dev, &config, usb_strings, 2, usbd_control_buffer, sizeof(usbd_control_buffer)); usbd_register_set_config_callback(usbd_dev, usb_set_config); while (true) { usbd_poll(usbd_dev); PT_YIELD(); } PT_END(); } static fibre_t usb_task = FIBRE_VAR_INIT(usb_fibre); static console_t uart_console; static void i2c_init(void) { /* clocks */ rcc_periph_clock_enable(RCC_GPIOB); rcc_periph_clock_enable(RCC_I2C1); rcc_periph_clock_enable(RCC_AFIO); /* initialize the peripheral */ i2c_ctx_t ctx; i2c_ctx_init(&ctx, I2C1); i2c_ctx_reset(&ctx); /* GPIO for I2C1 */ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_OPENDRAIN, GPIO6 | GPIO7); /* low-level pin for pulling !RESET of USB hub low */ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO8); gpio_clear(GPIOB, GPIO8); } static void jump_to_bootloader(void) { char *const marker = (char *)0x20004800; /* RAM@18K */ const char key[] = "remain-in-loader"; memcpy(marker, key, sizeof(key)); scb_reset_system(); /* Will never return. */ } static pt_state_t do_id(console_t *c) { char serial_no[25]; desig_get_unique_id_as_string(serial_no, sizeof(serial_no)); fprintf(c->out, "%s\n", serial_no); return PT_EXITED; } static pt_state_t do_reboot(console_t *c) { (void)c; jump_to_bootloader(); return PT_EXITED; } static uint16_t ncn7240_state = 0xffff; static void ncn7240_set_output(uint8_t chan, bool enable) { uint8_t nbits = (chan-1)*2; ncn7240_state &= ~(3 << nbits); if (enable) ncn7240_state |= 2 << nbits; else ncn7240_state |= 3 << nbits; spi_enable(SPI1); int i; for (i = 0; i < 0x8000; i++) __asm__("nop"); spi_write(SPI1, ncn7240_state); for (i = 0; i < 0x8000; i++) __asm__("nop"); spi_disable(SPI1); } static bool ncn7240_get_output(uint8_t chan) { uint8_t nbits = (chan-1)*2; uint16_t st = (ncn7240_state >> nbits) & 3; if (st == 2) return true; else return false; } static pt_state_t do_on(console_t *c) { int chan; if (c->argc < 2) { fprintf(c->out, "You have to specify the channel (0..7)\r\n"); return PT_EXITED; } chan = atoi(c->argv[1]); if (chan < 1 || chan > 8) { fprintf(c->out, "Chanel must be (1..8)\r\n"); return PT_EXITED; } ncn7240_set_output((uint8_t)chan, true); fprintf(c->out, "Enabled %d\r\n", chan); return PT_EXITED; } static pt_state_t do_off(console_t *c) { int chan; if (c->argc < 2) { fprintf(c->out, "You have to specify the channel (0..7)\r\n"); return PT_EXITED; } chan = atoi(c->argv[1]); if (chan < 1 || chan > 8) { fprintf(c->out, "Chanel must be (1..8)\r\n"); return PT_EXITED; } ncn7240_set_output((uint8_t)chan, false); fprintf(c->out, "Disabled %d\r\n", chan); return PT_EXITED; } static pt_state_t do_get(console_t *c) { bool st; int chan; if (c->argc >= 2) { chan = atoi(c->argv[1]); if (chan < 1 || chan > 8) { fprintf(c->out, "Chanel must be (1..8)\r\n"); return PT_EXITED; } st = ncn7240_get_output(chan); fprintf(c->out, "%u: %s\r\n", chan, st ? "ON" : "OFF"); } else { for (chan = 1; chan <= 8; chan++) { st = ncn7240_get_output(chan); fprintf(c->out, "%u: %s\r\n", chan, st ? "ON" : "OFF"); } } return PT_EXITED; } static const console_cmd_t cmds[] = { CONSOLE_CMD_VAR_INIT("id", do_id), CONSOLE_CMD_VAR_INIT("reboot", do_reboot), CONSOLE_CMD_VAR_INIT("on", do_on), CONSOLE_CMD_VAR_INIT("off", do_off), CONSOLE_CMD_VAR_INIT("get", do_get), }; static void spi_init(void) { rcc_periph_clock_enable(RCC_SPI1); /* configure !SS, SCK, MISO and MOSI as "SPI alternate function */ gpio_set_mode(GPIOA, GPIO_MODE_OUTPUT_50_MHZ, GPIO_CNF_OUTPUT_ALTFN_PUSHPULL, GPIO4 | GPIO5 | GPIO6 | GPIO7); //gpio_set_af(GPIOA, GPIO_AF1, GPIO4 | GPIO5 | GPIO6 | GPIO7); /* configure NCV_EN as output, high */ gpio_set_mode(GPIOB, GPIO_MODE_OUTPUT_2_MHZ, GPIO_CNF_OUTPUT_PUSHPULL, GPIO0); gpio_set(GPIOB, GPIO0); spi_reset(SPI1); spi_init_master(SPI1, SPI_CR1_BAUDRATE_FPCLK_DIV_64, SPI_CR1_CPOL_CLK_TO_0_WHEN_IDLE, SPI_CR1_CPHA_CLK_TRANSITION_2, SPI_CR1_DFF_16BIT, SPI_CR1_MSBFIRST); spi_enable_ss_output(SPI1); } int main(void) { unsigned int i; rcc_clock_setup_in_hse_8mhz_out_72mhz(); rcc_periph_clock_enable(RCC_GPIOA); rcc_periph_clock_enable(RCC_GPIOB); console_init(&uart_console, stdout); for (i=0; i