* Put something on screen * Use the 32bit watch_date_time repr to pass from JS * Implement periodic callbacks * Clear display on enabling * Hook up watch_set_led_color() to SVG (green-only) * Make debug output full-width * Remove default Emscripten canvas * Implement sleep and button clicks * Fix time zone conversion bug in beats-time app * Clean up warnings * Fix pin levels * Set time zone to browser value (if available) * Add basic backup data saving * Silence format specifier warnings in both targets * Remove unnecessary, copied files * Use RTC pointer to clear callbacks (if available) * Use preprocessor define to avoid hardcoding MOVEMENT_NUM_FACES * Change each face to const preprocessor definition * Remove Intl.DateTimeFormat usage * Update shell.html title, header * Add touch start/end event handlers on SVG buttons * Update shell.html * Update folder structure (shared, simulator, hardware under watch-library) * Tease out shared components from watch_slcd * Clean up simulator watch_slcd.c inline JS calls * Fix missing newlines at end of file * Add simulator warnings (except format, unused-paremter) * Implement remaining watch_rtc functions * Fix button bug on mouse down then drag out * Implement remaining watch_slcd functions * Link keyboard events to buttons (for keys A, L, M) * Rewrite event handling (mouse, touch, keyboard) in C * Set explicit text UTF-8 charset in shell.html * Address PR comments * Remove unused directories from include paths
		
			
				
	
	
		
			783 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			783 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			C
		
	
	
		
			Executable File
		
	
	
	
	
| 
 | |
| /**
 | |
|  * \file
 | |
|  *
 | |
|  * \brief Non-Volatile Memory Controller
 | |
|  *
 | |
|  * Copyright (c) 2015-2018 Microchip Technology Inc. and its subsidiaries.
 | |
|  *
 | |
|  * \asf_license_start
 | |
|  *
 | |
|  * \page License
 | |
|  *
 | |
|  * Subject to your compliance with these terms, you may use Microchip
 | |
|  * software and any derivatives exclusively with Microchip products.
 | |
|  * It is your responsibility to comply with third party license terms applicable
 | |
|  * to your use of third party software (including open source software) that
 | |
|  * may accompany Microchip software.
 | |
|  *
 | |
|  * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES,
 | |
|  * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE,
 | |
|  * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY,
 | |
|  * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE
 | |
|  * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL
 | |
|  * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE
 | |
|  * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE
 | |
|  * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE FULLEST EXTENT
 | |
|  * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY
 | |
|  * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY,
 | |
|  * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 | |
|  *
 | |
|  * \asf_license_stop
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <hpl_flash.h>
 | |
| #include <hpl_user_area.h>
 | |
| #include <string.h>
 | |
| #include <utils_assert.h>
 | |
| #include <utils.h>
 | |
| #include <hpl_nvmctrl_config.h>
 | |
| 
 | |
| #define NVM_MEMORY ((volatile uint16_t *)FLASH_ADDR)
 | |
| 
 | |
| /**
 | |
|  * \brief NVM configuration type
 | |
|  */
 | |
| struct nvm_configuration {
 | |
| 	hri_nvmctrl_ctrlb_reg_t ctrlb; /*!< Control B Register */
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * \brief Array of NVM configurations
 | |
|  */
 | |
| static struct nvm_configuration _nvm
 | |
|     = {(CONF_NVM_CACHE << NVMCTRL_CTRLB_CACHEDIS_Pos) | (CONF_NVM_READ_MODE << NVMCTRL_CTRLB_READMODE_Pos)
 | |
|        | (CONF_NVM_SLEEPPRM << NVMCTRL_CTRLB_SLEEPPRM_Pos)};
 | |
| 
 | |
| /*!< Pointer to hpl device */
 | |
| static struct _flash_device *_nvm_dev = NULL;
 | |
| 
 | |
| static void _flash_erase_row(void *const hw, const uint32_t dst_addr, uint32_t nvmctrl_cmd);
 | |
| static void _flash_program(void *const hw, const uint32_t dst_addr, const uint8_t *buffer, const uint16_t size,
 | |
|                            uint32_t nvmctrl_cmd);
 | |
| 
 | |
| /**
 | |
|  * \brief Initialize NVM
 | |
|  */
 | |
| int32_t _flash_init(struct _flash_device *const device, void *const hw)
 | |
| {
 | |
| 	ASSERT(device && (hw == NVMCTRL));
 | |
| 	uint32_t ctrlb;
 | |
| 
 | |
| 	device->hw = hw;
 | |
| 	ctrlb      = _nvm.ctrlb & ~(NVMCTRL_CTRLB_RWS_Msk | NVMCTRL_CTRLB_MANW);
 | |
| 	ctrlb |= hri_nvmctrl_get_CTRLB_reg(device->hw, NVMCTRL_CTRLB_RWS_Msk | NVMCTRL_CTRLB_MANW);
 | |
| 	hri_nvmctrl_write_CTRLB_reg(device->hw, ctrlb);
 | |
| 
 | |
| 	_nvm_dev = device;
 | |
| 	NVIC_DisableIRQ(NVMCTRL_IRQn);
 | |
| 	NVIC_ClearPendingIRQ(NVMCTRL_IRQn);
 | |
| 	NVIC_EnableIRQ(NVMCTRL_IRQn);
 | |
| 	return ERR_NONE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief De-initialize NVM
 | |
|  */
 | |
| void _flash_deinit(struct _flash_device *const device)
 | |
| {
 | |
| 	device->hw = NULL;
 | |
| 	NVIC_DisableIRQ(NVMCTRL_IRQn);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Get the flash page size.
 | |
|  */
 | |
| uint32_t _flash_get_page_size(struct _flash_device *const device)
 | |
| {
 | |
| 	(void)device;
 | |
| 	return (uint32_t)NVMCTRL_PAGE_SIZE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Get the numbers of flash page.
 | |
|  */
 | |
| uint32_t _flash_get_total_pages(struct _flash_device *const device)
 | |
| {
 | |
| 	(void)device;
 | |
| 	return (uint32_t)FLASH_NB_OF_PAGES;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Get the number of wait states for read and write operations.
 | |
|  */
 | |
| uint8_t _flash_get_wait_state(struct _flash_device *const device)
 | |
| {
 | |
| 	return hri_nvmctrl_get_CTRLB_reg(device->hw, NVMCTRL_CTRLB_RWS_Msk);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Set the number of wait states for read and write operations.
 | |
|  */
 | |
| void _flash_set_wait_state(struct _flash_device *const device, uint8_t state)
 | |
| {
 | |
| 	hri_nvmctrl_write_CTRLB_RWS_bf(device->hw, state);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Reads a number of bytes to a page in the internal Flash.
 | |
|  */
 | |
| void _flash_read(struct _flash_device *const device, const uint32_t src_addr, uint8_t *buffer, uint32_t length)
 | |
| {
 | |
| 	uint32_t nvm_address = src_addr / 2;
 | |
| 	uint32_t i;
 | |
| 	uint16_t data;
 | |
| 
 | |
| 	/* Check if the module is busy */
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(device->hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* Clear flags */
 | |
| 	hri_nvmctrl_clear_STATUS_reg(device->hw, NVMCTRL_STATUS_MASK);
 | |
| 
 | |
| 	/* Check whether byte address is word-aligned*/
 | |
| 	if (src_addr % 2) {
 | |
| 		data      = NVM_MEMORY[nvm_address++];
 | |
| 		buffer[0] = data >> 8;
 | |
| 		i         = 1;
 | |
| 	} else {
 | |
| 		i = 0;
 | |
| 	}
 | |
| 
 | |
| 	/* NVM _must_ be accessed as a series of 16-bit words, perform manual copy
 | |
| 	 * to ensure alignment */
 | |
| 	while (i < length) {
 | |
| 		data      = NVM_MEMORY[nvm_address++];
 | |
| 		buffer[i] = (data & 0xFF);
 | |
| 		if (i < (length - 1)) {
 | |
| 			buffer[i + 1] = (data >> 8);
 | |
| 		}
 | |
| 		i += 2;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Writes a number of bytes to a page in the internal Flash.
 | |
|  */
 | |
| void _flash_write(struct _flash_device *const device, const uint32_t dst_addr, uint8_t *buffer, uint32_t length)
 | |
| {
 | |
| 	uint8_t  tmp_buffer[NVMCTRL_ROW_PAGES][NVMCTRL_PAGE_SIZE];
 | |
| 	uint32_t row_start_addr, row_end_addr;
 | |
| 	uint32_t i, j, k;
 | |
| 	uint32_t wr_start_addr = dst_addr;
 | |
| 
 | |
| 	do {
 | |
| 		row_start_addr = wr_start_addr & ~((NVMCTRL_PAGE_SIZE * NVMCTRL_ROW_PAGES) - 1);
 | |
| 		row_end_addr   = row_start_addr + NVMCTRL_ROW_PAGES * NVMCTRL_PAGE_SIZE - 1;
 | |
| 
 | |
| 		/* store the erase data into temp buffer before write */
 | |
| 		for (i = 0; i < NVMCTRL_ROW_PAGES; i++) {
 | |
| 			_flash_read(device, row_start_addr + i * NVMCTRL_PAGE_SIZE, tmp_buffer[i], NVMCTRL_PAGE_SIZE);
 | |
| 		}
 | |
| 
 | |
| 		/* temp buffer update */
 | |
| 		j = (wr_start_addr - row_start_addr) / NVMCTRL_PAGE_SIZE;
 | |
| 		k = wr_start_addr - row_start_addr - j * NVMCTRL_PAGE_SIZE;
 | |
| 		while ((wr_start_addr <= row_end_addr) && (length > 0)) {
 | |
| 			tmp_buffer[j][k] = *buffer;
 | |
| 			k                = (k + 1) % NVMCTRL_PAGE_SIZE;
 | |
| 			if (0 == k) {
 | |
| 				j++;
 | |
| 			}
 | |
| 			wr_start_addr++;
 | |
| 			buffer++;
 | |
| 			length--;
 | |
| 		}
 | |
| 
 | |
| 		/* erase row before write */
 | |
| 		_flash_erase_row(device->hw, row_start_addr, NVMCTRL_CTRLA_CMD_ER);
 | |
| 
 | |
| 		/* write buffer to flash */
 | |
| 		for (i = 0; i < NVMCTRL_ROW_PAGES; i++) {
 | |
| 			_flash_program(device->hw,
 | |
| 			               row_start_addr + i * NVMCTRL_PAGE_SIZE,
 | |
| 			               tmp_buffer[i],
 | |
| 			               NVMCTRL_PAGE_SIZE,
 | |
| 			               NVMCTRL_CTRLA_CMD_WP);
 | |
| 		}
 | |
| 
 | |
| 	} while (row_end_addr < (wr_start_addr + length - 1));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Appends a number of bytes in the internal Flash.
 | |
|  */
 | |
| void _flash_append(struct _flash_device *const device, const uint32_t dst_addr, uint8_t *buffer, uint32_t length)
 | |
| {
 | |
| 	uint32_t page_start_addr = dst_addr & ~(NVMCTRL_PAGE_SIZE - 1);
 | |
| 	uint32_t size;
 | |
| 	uint32_t offset = 0;
 | |
| 
 | |
| 	if (dst_addr != page_start_addr) {
 | |
| 		/* Need to write some data to the end of a page */
 | |
| 		size = min(length, NVMCTRL_PAGE_SIZE - (dst_addr - page_start_addr));
 | |
| 		_flash_program(device->hw, dst_addr, buffer, size, NVMCTRL_CTRLA_CMD_WP);
 | |
| 		page_start_addr += NVMCTRL_PAGE_SIZE;
 | |
| 		offset += size;
 | |
| 	}
 | |
| 
 | |
| 	while (offset < length) {
 | |
| 		size = min(length - offset, NVMCTRL_PAGE_SIZE);
 | |
| 		_flash_program(device->hw, page_start_addr, buffer + offset, size, NVMCTRL_CTRLA_CMD_WP);
 | |
| 		page_start_addr += NVMCTRL_PAGE_SIZE;
 | |
| 		offset += size;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Execute erase in the internal flash
 | |
|  */
 | |
| void _flash_erase(struct _flash_device *const device, uint32_t dst_addr, uint32_t page_nums)
 | |
| {
 | |
| 	uint8_t  tmp_buffer[NVMCTRL_PAGE_SIZE];
 | |
| 	uint32_t row_start_addr;
 | |
| 	uint32_t i;
 | |
| 
 | |
| 	row_start_addr = dst_addr & ~((NVMCTRL_PAGE_SIZE * NVMCTRL_ROW_PAGES) - 1);
 | |
| 
 | |
| 	memset(tmp_buffer, 0xFF, NVMCTRL_PAGE_SIZE);
 | |
| 
 | |
| 	/* when address is not aligned with row start address */
 | |
| 	if (dst_addr != row_start_addr) {
 | |
| 		row_start_addr += NVMCTRL_ROW_PAGES * NVMCTRL_PAGE_SIZE;
 | |
| 		for (i = 0; i < NVMCTRL_ROW_PAGES - 1; i++) {
 | |
| 			_flash_write(device, dst_addr, tmp_buffer, NVMCTRL_PAGE_SIZE);
 | |
| 			if (--page_nums == 0) {
 | |
| 				return;
 | |
| 			}
 | |
| 			dst_addr += NVMCTRL_PAGE_SIZE;
 | |
| 			if (dst_addr == row_start_addr) {
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	while (page_nums >= NVMCTRL_ROW_PAGES) {
 | |
| 		_flash_erase_row(device->hw, row_start_addr, NVMCTRL_CTRLA_CMD_ER);
 | |
| 		row_start_addr += NVMCTRL_ROW_PAGES * NVMCTRL_PAGE_SIZE;
 | |
| 		page_nums -= NVMCTRL_ROW_PAGES;
 | |
| 	}
 | |
| 
 | |
| 	if (page_nums != 0) {
 | |
| 		for (i = 0; i < page_nums; i++) {
 | |
| 			_flash_write(device, row_start_addr, tmp_buffer, NVMCTRL_PAGE_SIZE);
 | |
| 			row_start_addr += NVMCTRL_PAGE_SIZE;
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Execute lock in the internal flash
 | |
|  */
 | |
| int32_t _flash_lock(struct _flash_device *const device, const uint32_t dst_addr, uint32_t page_nums)
 | |
| {
 | |
| 	uint32_t region_pages;
 | |
| 	uint32_t row_start_addr;
 | |
| 
 | |
| 	region_pages   = (uint32_t)NVMCTRL_FLASH_SIZE / (16 * NVMCTRL_PAGE_SIZE);
 | |
| 	row_start_addr = dst_addr & ~((NVMCTRL_PAGE_SIZE * NVMCTRL_ROW_PAGES) - 1);
 | |
| 
 | |
| 	if ((page_nums != region_pages) || (dst_addr != row_start_addr)) {
 | |
| 		return ERR_INVALID_ARG;
 | |
| 	}
 | |
| 
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(device->hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* Clear flags */
 | |
| 	hri_nvmctrl_clear_STATUS_reg(device->hw, NVMCTRL_STATUS_MASK);
 | |
| 
 | |
| 	hri_nvmctrl_write_ADDR_reg(device->hw, dst_addr / 2);
 | |
| 	hri_nvmctrl_write_CTRLA_reg(device->hw, NVMCTRL_CTRLA_CMD_LR | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| 
 | |
| 	return (int32_t)NVMCTRL_FLASH_SIZE / (16 * NVMCTRL_PAGE_SIZE);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Execute unlock in the internal flash
 | |
|  */
 | |
| int32_t _flash_unlock(struct _flash_device *const device, const uint32_t dst_addr, uint32_t page_nums)
 | |
| {
 | |
| 	uint32_t region_pages;
 | |
| 	uint32_t row_start_addr;
 | |
| 
 | |
| 	region_pages   = (uint32_t)NVMCTRL_FLASH_SIZE / (16 * NVMCTRL_PAGE_SIZE);
 | |
| 	row_start_addr = dst_addr & ~((NVMCTRL_PAGE_SIZE * NVMCTRL_ROW_PAGES) - 1);
 | |
| 
 | |
| 	if ((page_nums != region_pages) || (dst_addr != row_start_addr)) {
 | |
| 		return ERR_INVALID_ARG;
 | |
| 	}
 | |
| 
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(device->hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* Clear flags */
 | |
| 	hri_nvmctrl_clear_STATUS_reg(device->hw, NVMCTRL_STATUS_MASK);
 | |
| 
 | |
| 	hri_nvmctrl_write_ADDR_reg(device->hw, dst_addr / 2);
 | |
| 	hri_nvmctrl_write_CTRLA_reg(device->hw, NVMCTRL_CTRLA_CMD_UR | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| 
 | |
| 	return (int32_t)NVMCTRL_FLASH_SIZE / (16 * NVMCTRL_PAGE_SIZE);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief check whether the region which is pointed by address
 | |
|  */
 | |
| bool _flash_is_locked(struct _flash_device *const device, const uint32_t dst_addr)
 | |
| {
 | |
| 	uint16_t region_id;
 | |
| 
 | |
| 	/* Get region for given page */
 | |
| 	region_id = dst_addr / (NVMCTRL_FLASH_SIZE / 16);
 | |
| 
 | |
| 	return !(hri_nvmctrl_get_LOCK_reg(device->hw, 1 << region_id));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Enable/disable Flash interrupt
 | |
|  */
 | |
| void _flash_set_irq_state(struct _flash_device *const device, const enum _flash_cb_type type, const bool state)
 | |
| {
 | |
| 	ASSERT(device);
 | |
| 
 | |
| 	if (FLASH_DEVICE_CB_READY == type) {
 | |
| 		hri_nvmctrl_write_INTEN_READY_bit(device->hw, state);
 | |
| 	} else if (FLASH_DEVICE_CB_ERROR == type) {
 | |
| 		hri_nvmctrl_write_INTEN_ERROR_bit(device->hw, state);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \internal   erase a row in flash
 | |
|  * \param[in]  hw            The pointer to hardware instance
 | |
|  * \param[in]  dst_addr      Destination page address to erase
 | |
|  */
 | |
| static void _flash_erase_row(void *const hw, const uint32_t dst_addr, uint32_t nvmctrl_cmd)
 | |
| {
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* Clear flags */
 | |
| 	hri_nvmctrl_clear_STATUS_reg(hw, NVMCTRL_STATUS_MASK);
 | |
| 
 | |
| 	/* Set address and command */
 | |
| 	hri_nvmctrl_write_ADDR_reg(hw, dst_addr / 2);
 | |
| 	hri_nvmctrl_write_CTRLA_reg(hw, nvmctrl_cmd | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \internal   write a page in flash
 | |
|  * \param[in]  hw            The pointer to hardware instance
 | |
|  * \param[in]  dst_addr      Destination page address to write
 | |
|  * \param[in]  buffer        Pointer to buffer where the data to
 | |
|  *                           write is stored
 | |
|  * \param[in] size           The size of data to write to a page
 | |
|  */
 | |
| static void _flash_program(void *const hw, const uint32_t dst_addr, const uint8_t *buffer, const uint16_t size,
 | |
|                            uint32_t nvmctrl_cmd)
 | |
| {
 | |
| 	ASSERT(!(dst_addr % 2));
 | |
| 
 | |
| 	uint32_t nvm_address = dst_addr / 2;
 | |
| 	uint16_t i, data;
 | |
| 
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	hri_nvmctrl_write_CTRLA_reg(hw, NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| 
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* Clear flags */
 | |
| 	hri_nvmctrl_clear_STATUS_reg(hw, NVMCTRL_STATUS_MASK);
 | |
| 
 | |
| 	for (i = 0; i < size; i += 2) {
 | |
| 		data = buffer[i];
 | |
| 		if (i < NVMCTRL_PAGE_SIZE - 1) {
 | |
| 			data |= (buffer[i + 1] << 8);
 | |
| 		}
 | |
| 		NVM_MEMORY[nvm_address++] = data;
 | |
| 	}
 | |
| 
 | |
| 	while (!hri_nvmctrl_get_interrupt_READY_bit(hw)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	hri_nvmctrl_write_ADDR_reg(hw, dst_addr / 2);
 | |
| 	hri_nvmctrl_write_CTRLA_reg(hw, nvmctrl_cmd | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \internal NVM interrupt handler
 | |
|  */
 | |
| void NVMCTRL_Handler(void)
 | |
| {
 | |
| 	void *const hw = _nvm_dev->hw;
 | |
| 
 | |
| 	if (hri_nvmctrl_get_interrupt_READY_bit(hw)) {
 | |
| 		if (NULL != _nvm_dev->flash_cb.ready_cb) {
 | |
| 			_nvm_dev->flash_cb.ready_cb(_nvm_dev);
 | |
| 		}
 | |
| 	} else if (hri_nvmctrl_get_interrupt_ERROR_bit(hw)) {
 | |
| 		hri_nvmctrl_clear_interrupt_ERROR_bit(hw);
 | |
| 		if (NULL != _nvm_dev->flash_cb.error_cb) {
 | |
| 			_nvm_dev->flash_cb.error_cb(_nvm_dev);
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
| The NVM User Row contains calibration data that are automatically read at device
 | |
| power on.
 | |
| The NVM User Row can be read at address 0x804000.
 | |
| */
 | |
| #ifndef _NVM_USER_ROW_BASE
 | |
| #define _NVM_USER_ROW_BASE 0x804000
 | |
| #endif
 | |
| #define _NVM_USER_ROW_N_BITS 64
 | |
| #define _NVM_USER_ROW_N_BYTES (_NVM_USER_ROW_N_BITS / 8)
 | |
| #define _NVM_USER_ROW_END (((uint8_t *)_NVM_USER_ROW_BASE) + _NVM_USER_ROW_N_BYTES - 1)
 | |
| #define _IS_NVM_USER_ROW(b)                                                                                            \
 | |
| 	(((uint8_t *)(b) >= (uint8_t *)(_NVM_USER_ROW_BASE)) && ((uint8_t *)(b) <= (uint8_t *)(_NVM_USER_ROW_END)))
 | |
| #define _IN_NVM_USER_ROW(b, o) (((uint8_t *)(b) + (o)) <= (uint8_t *)(_NVM_USER_ROW_END))
 | |
| 
 | |
| /*
 | |
| The NVM Software Calibration Area can be read at address 0x806020.
 | |
| The NVM Software Calibration Area can not be written.
 | |
| */
 | |
| #ifndef _NVM_SW_CALIB_AREA_BASE
 | |
| #define _NVM_SW_CALIB_AREA_BASE 0x806020
 | |
| #endif
 | |
| #define _NVM_SW_CALIB_AREA_N_BITS 128
 | |
| #define _NVM_SW_CALIB_AREA_N_BYTES (_NVM_SW_CALIB_AREA_N_BITS / 8)
 | |
| #define _NVM_SW_CALIB_AREA_END (((uint8_t *)_NVM_SW_CALIB_AREA_BASE) + _NVM_SW_CALIB_AREA_N_BYTES - 1)
 | |
| #define _IS_NVM_SW_CALIB_AREA(b)                                                                                       \
 | |
| 	(((uint8_t *)(b) >= (uint8_t *)_NVM_SW_CALIB_AREA_BASE) && ((uint8_t *)(b) <= (uint8_t *)_NVM_SW_CALIB_AREA_END))
 | |
| #define _IN_NVM_SW_CALIB_AREA(b, o) (((uint8_t *)(b) + (o)) <= (uint8_t *)(_NVM_SW_CALIB_AREA_END))
 | |
| 
 | |
| /**
 | |
|  * \internal Read left aligned data bits
 | |
|  * \param[in] base       Base address for the data
 | |
|  * \param[in] bit_offset Offset for the bitfield start
 | |
|  * \param[in] n_bits     Number of bits in the bitfield
 | |
|  */
 | |
| static inline uint32_t _user_area_read_l32_bits(const volatile uint32_t *base, const uint32_t bit_offset,
 | |
|                                                 const uint8_t n_bits)
 | |
| {
 | |
| 	return base[bit_offset >> 5] & ((1 << n_bits) - 1);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \internal Read right aligned data bits
 | |
|  * \param[in] base       Base address for the data
 | |
|  * \param[in] bit_offset Offset for the bitfield start
 | |
|  * \param[in] n_bits     Number of bits in the bitfield
 | |
|  */
 | |
| static inline uint32_t _user_area_read_r32_bits(const volatile uint32_t *base, const uint32_t bit_offset,
 | |
|                                                 const uint8_t n_bits)
 | |
| {
 | |
| 	return (base[bit_offset >> 5] >> (bit_offset & 0x1F)) & ((1 << n_bits) - 1);
 | |
| }
 | |
| 
 | |
| int32_t _user_area_read(const void *base, const uint32_t offset, uint8_t *buf, uint32_t size)
 | |
| {
 | |
| 	ASSERT(buf);
 | |
| 
 | |
| 	/** Parameter check. */
 | |
| 	if (_IS_NVM_USER_ROW(base)) {
 | |
| 		if (!_IN_NVM_USER_ROW(base, offset)) {
 | |
| 			return ERR_BAD_ADDRESS;
 | |
| 		}
 | |
| 		/* Cut off if request too many bytes */
 | |
| 		if (!_IN_NVM_USER_ROW(base, offset + size - 1)) {
 | |
| 			return ERR_INVALID_ARG;
 | |
| 		}
 | |
| 	} else if (_IS_NVM_SW_CALIB_AREA(base)) {
 | |
| 		if (!_IN_NVM_SW_CALIB_AREA(base, offset)) {
 | |
| 			return ERR_BAD_ADDRESS;
 | |
| 		}
 | |
| 		/* Cut off if request too many bytes */
 | |
| 		if (!_IN_NVM_SW_CALIB_AREA(base, offset + size - 1)) {
 | |
| 			return ERR_INVALID_ARG;
 | |
| 		}
 | |
| 	} else {
 | |
| 		return ERR_UNSUPPORTED_OP;
 | |
| 	}
 | |
| 
 | |
| 	/* Copy data */
 | |
| 	memcpy(buf, ((uint8_t *)base) + offset, size);
 | |
| 	return ERR_NONE;
 | |
| }
 | |
| 
 | |
| uint32_t _user_area_read_bits(const void *base, const uint32_t bit_offset, const uint8_t n_bits)
 | |
| {
 | |
| 	volatile uint32_t *mem_base = (volatile uint32_t *)base;
 | |
| 	uint32_t           l_off, l_bits;
 | |
| 	uint32_t           r_off, r_bits;
 | |
| 
 | |
| 	/** Parameter check. */
 | |
| 	if (_IS_NVM_USER_ROW(base)) {
 | |
| 		ASSERT(_IN_NVM_USER_ROW(base, bit_offset >> 3) && _IN_NVM_USER_ROW(base, (bit_offset + n_bits - 1) >> 3));
 | |
| 	} else if (_IS_NVM_SW_CALIB_AREA(base)) {
 | |
| 		ASSERT(_IN_NVM_SW_CALIB_AREA(base, bit_offset >> 3)
 | |
| 		       && _IN_NVM_SW_CALIB_AREA(base, (bit_offset + n_bits - 1) >> 3));
 | |
| 	} else {
 | |
| 		ASSERT(false);
 | |
| 	}
 | |
| 
 | |
| 	/* Since the bitfield can cross 32-bits boundaries,
 | |
| 	 * left and right bits are read from 32-bit aligned address
 | |
| 	 * and then combined together. */
 | |
| 	l_off  = bit_offset & (~(32 - 1));
 | |
| 	r_off  = l_off + 32;
 | |
| 	l_bits = 32 - (bit_offset & (32 - 1));
 | |
| 	if (n_bits > l_bits) {
 | |
| 		r_bits = n_bits - l_bits;
 | |
| 	} else {
 | |
| 		l_bits = n_bits;
 | |
| 		r_bits = 0;
 | |
| 	}
 | |
| 	return _user_area_read_r32_bits(mem_base, bit_offset, l_bits)
 | |
| 	       + (_user_area_read_l32_bits(mem_base, r_off, r_bits) << l_bits);
 | |
| }
 | |
| 
 | |
| /** \internal Write 64-bit user row
 | |
|  *  \param[in] _row Pointer to 64-bit user row data.
 | |
|  */
 | |
| static int32_t _user_row_write_exec(const uint32_t *_row)
 | |
| {
 | |
| 	Nvmctrl *hw    = NVMCTRL;
 | |
| 	uint32_t ctrlb = hri_nvmctrl_read_CTRLB_reg(NVMCTRL);
 | |
| 
 | |
| 	/* Denie if Security Bit is set */
 | |
| 	if (hri_nvmctrl_get_STATUS_reg(hw, NVMCTRL_STATUS_SB)) {
 | |
| 		return ERR_DENIED;
 | |
| 	}
 | |
| 
 | |
| 	/* Do Save */
 | |
| 
 | |
| 	/* - Prepare. */
 | |
| 	while (!hri_nvmctrl_get_INTFLAG_reg(hw, NVMCTRL_INTFLAG_READY)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 	hri_nvmctrl_clear_STATUS_reg(hw, NVMCTRL_STATUS_MASK);
 | |
| 	hri_nvmctrl_set_CTRLB_MANW_bit(hw);
 | |
| 
 | |
| 	/* - Erase AUX row. */
 | |
| 	hri_nvmctrl_write_ADDR_reg(hw, (hri_nvmctrl_addr_reg_t)(_NVM_USER_ROW_BASE / 2));
 | |
| 	hri_nvmctrl_write_CTRLA_reg(hw, NVMCTRL_CTRLA_CMD_EAR | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| 	while (!hri_nvmctrl_get_INTFLAG_reg(hw, NVMCTRL_INTFLAG_READY)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* - Page buffer clear & write. */
 | |
| 	hri_nvmctrl_write_CTRLA_reg(hw, NVMCTRL_CTRLA_CMD_PBC | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| 	while (!hri_nvmctrl_get_INTFLAG_reg(hw, NVMCTRL_INTFLAG_READY)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 	*((uint32_t *)NVMCTRL_AUX0_ADDRESS)       = _row[0];
 | |
| 	*(((uint32_t *)NVMCTRL_AUX0_ADDRESS) + 1) = _row[1];
 | |
| 
 | |
| 	/* - Write AUX row. */
 | |
| 	hri_nvmctrl_write_ADDR_reg(hw, (hri_nvmctrl_addr_reg_t)(_NVM_USER_ROW_BASE / 2));
 | |
| 	hri_nvmctrl_write_CTRLA_reg(hw, NVMCTRL_CTRLA_CMD_WAP | NVMCTRL_CTRLA_CMDEX_KEY);
 | |
| 	while (!hri_nvmctrl_get_INTFLAG_reg(hw, NVMCTRL_INTFLAG_READY)) {
 | |
| 		/* Wait until this module isn't busy */
 | |
| 	}
 | |
| 
 | |
| 	/* Restore CTRLB */
 | |
| 	hri_nvmctrl_write_CTRLB_reg(NVMCTRL, ctrlb);
 | |
| 
 | |
| 	return ERR_NONE;
 | |
| }
 | |
| 
 | |
| int32_t _user_area_write(void *base, const uint32_t offset, const uint8_t *buf, const uint32_t size)
 | |
| {
 | |
| 	uint32_t _row[2]; /* Copy of user row. */
 | |
| 
 | |
| 	/** Parameter check. */
 | |
| 	if (_IS_NVM_USER_ROW(base)) {
 | |
| 		if (!_IN_NVM_USER_ROW(base, offset)) {
 | |
| 			return ERR_BAD_ADDRESS;
 | |
| 		} else if (!_IN_NVM_USER_ROW(base, offset + size - 1)) {
 | |
| 			return ERR_INVALID_ARG;
 | |
| 		}
 | |
| 	} else if (_IS_NVM_SW_CALIB_AREA(base)) {
 | |
| 		return ERR_DENIED;
 | |
| 	} else {
 | |
| 		return ERR_UNSUPPORTED_OP;
 | |
| 	}
 | |
| 
 | |
| 	memcpy(_row, base, 8);                       /* Store previous data. */
 | |
| 	memcpy((uint8_t *)_row + offset, buf, size); /* Modify with buf data. */
 | |
| 
 | |
| 	return _user_row_write_exec(_row);
 | |
| }
 | |
| 
 | |
| int32_t _user_area_write_bits(void *base, const uint32_t bit_offset, const uint32_t bits, const uint8_t n_bits)
 | |
| {
 | |
| 	uint32_t _row[2]; /* Copy of user row. */
 | |
| 	uint32_t l_off, l_bits;
 | |
| 	uint32_t r_off, r_bits;
 | |
| 
 | |
| 	/** Parameter check. */
 | |
| 	if (_IS_NVM_USER_ROW(base)) {
 | |
| 		if (!_IN_NVM_USER_ROW(base, bit_offset >> 3)) {
 | |
| 			return ERR_BAD_ADDRESS;
 | |
| 		} else if (!_IN_NVM_USER_ROW(base, (bit_offset + n_bits - 1) >> 3)) {
 | |
| 			return ERR_INVALID_ARG;
 | |
| 		}
 | |
| 	} else if (_IS_NVM_SW_CALIB_AREA(base)) {
 | |
| 		return ERR_DENIED;
 | |
| 	} else {
 | |
| 		return ERR_UNSUPPORTED_OP;
 | |
| 	}
 | |
| 
 | |
| 	/* Since the bitfield can cross 32-bits boundaries,
 | |
| 	 * left and right bits are splitted for 32-bit aligned address
 | |
| 	 * and then saved. */
 | |
| 	l_off  = bit_offset & (~(32 - 1));
 | |
| 	r_off  = l_off + 32;
 | |
| 	l_bits = 32 - (bit_offset & (32 - 1));
 | |
| 	if (n_bits > l_bits) {
 | |
| 		r_bits = n_bits - l_bits;
 | |
| 	} else {
 | |
| 		l_bits = n_bits;
 | |
| 		r_bits = 0;
 | |
| 	}
 | |
| 
 | |
| 	memcpy(_row, base, 8); /* Store previous data. */
 | |
| 	if (l_bits) {
 | |
| 		uint32_t l_mask = ((1 << l_bits) - 1) << (bit_offset & (32 - 1));
 | |
| 		_row[bit_offset >> 5] &= ~l_mask;
 | |
| 		_row[bit_offset >> 5] |= (bits << (bit_offset & (32 - 1))) & l_mask;
 | |
| 	}
 | |
| 	if (r_bits) {
 | |
| 		uint32_t r_mask = (1 << r_bits) - 1;
 | |
| 		_row[r_off >> 5] &= ~r_mask;
 | |
| 		_row[r_off >> 5] |= bits >> l_bits;
 | |
| 	}
 | |
| 	return _user_row_write_exec(_row);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Return if given address is in Flash RWWEE array range.
 | |
|  */
 | |
| static bool _is_valid_rww_flash_address(uint32_t addr)
 | |
| {
 | |
| #define RWWEE_ADDR_START NVMCTRL_RWW_EEPROM_ADDR
 | |
| #define RWWEE_ADDR_END (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES)
 | |
| 
 | |
| 	if ((addr < NVMCTRL_RWW_EEPROM_ADDR)
 | |
| 	    || (addr > (NVMCTRL_RWW_EEPROM_ADDR + NVMCTRL_PAGE_SIZE * NVMCTRL_RWWEE_PAGES))) {
 | |
| 		return false;
 | |
| 	}
 | |
| 	return true;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Get the RWWEE flash page size.
 | |
|  */
 | |
| uint32_t _rww_flash_get_page_size(struct _flash_device *const device)
 | |
| {
 | |
| 	(void)device;
 | |
| 	return (uint32_t)NVMCTRL_PAGE_SIZE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Get the total page numbers of RWWEE flash.
 | |
|  */
 | |
| uint32_t _rww_flash_get_total_pages(struct _flash_device *const device)
 | |
| {
 | |
| 	(void)device;
 | |
| 	return (uint32_t)NVMCTRL_RWWEE_PAGES;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Reads a number of bytes in the internal RWWEE Flash.
 | |
|  */
 | |
| int32_t _rww_flash_read(struct _flash_device *const device, const uint32_t src_addr, uint8_t *buffer, uint32_t length)
 | |
| {
 | |
| 	/* Check if the address is valid */
 | |
| 	if (!_is_valid_rww_flash_address(src_addr) || !_is_valid_rww_flash_address(src_addr + length)) {
 | |
| 		return ERR_BAD_ADDRESS;
 | |
| 	}
 | |
| 
 | |
| 	_flash_read(device, src_addr, buffer, length);
 | |
| 
 | |
| 	return ERR_NONE;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * \brief Writes a number of bytes in the internal RWWEE Flash.
 | |
|  */
 | |
| int32_t _rww_flash_write(struct _flash_device *const device, const uint32_t dst_addr, uint8_t *buffer, uint32_t length)
 | |
| {
 | |
| 	uint8_t  tmp_buffer[NVMCTRL_ROW_PAGES][NVMCTRL_PAGE_SIZE];
 | |
| 	uint32_t row_start_addr, row_end_addr;
 | |
| 	uint32_t i, j, k;
 | |
| 	uint32_t wr_start_addr = dst_addr;
 | |
| 
 | |
| 	/* Check if the address is valid */
 | |
| 	if (!_is_valid_rww_flash_address(dst_addr) || !_is_valid_rww_flash_address(dst_addr + length)) {
 | |
| 		return ERR_BAD_ADDRESS;
 | |
| 	}
 | |
| 
 | |
| 	do {
 | |
| 		row_start_addr = wr_start_addr & ~((NVMCTRL_PAGE_SIZE * NVMCTRL_ROW_PAGES) - 1);
 | |
| 		row_end_addr   = row_start_addr + NVMCTRL_ROW_PAGES * NVMCTRL_PAGE_SIZE - 1;
 | |
| 
 | |
| 		/* store the erase data into temp buffer before write */
 | |
| 		for (i = 0; i < NVMCTRL_ROW_PAGES; i++) {
 | |
| 			_rww_flash_read(device, row_start_addr + i * NVMCTRL_PAGE_SIZE, tmp_buffer[i], NVMCTRL_PAGE_SIZE);
 | |
| 		}
 | |
| 
 | |
| 		/* temp buffer update */
 | |
| 		j = (wr_start_addr - row_start_addr) / NVMCTRL_PAGE_SIZE;
 | |
| 		k = wr_start_addr - row_start_addr - j * NVMCTRL_PAGE_SIZE;
 | |
| 		while ((wr_start_addr <= row_end_addr) && (length > 0)) {
 | |
| 			tmp_buffer[j][k] = *buffer;
 | |
| 			k                = (k + 1) % NVMCTRL_PAGE_SIZE;
 | |
| 			if (0 == k) {
 | |
| 				j++;
 | |
| 			}
 | |
| 			wr_start_addr++;
 | |
| 			buffer++;
 | |
| 			length--;
 | |
| 		}
 | |
| 
 | |
| 		/* erase row before write */
 | |
| 		_flash_erase_row(device->hw, row_start_addr, NVMCTRL_CTRLA_CMD_RWWEEER);
 | |
| 
 | |
| 		/* write buffer to flash */
 | |
| 		for (i = 0; i < NVMCTRL_ROW_PAGES; i++) {
 | |
| 			_flash_program(device->hw,
 | |
| 			               row_start_addr + i * NVMCTRL_PAGE_SIZE,
 | |
| 			               tmp_buffer[i],
 | |
| 			               NVMCTRL_PAGE_SIZE,
 | |
| 			               NVMCTRL_CTRLA_CMD_RWWEEWP);
 | |
| 		}
 | |
| 
 | |
| 	} while (row_end_addr < (wr_start_addr + length - 1));
 | |
| 
 | |
| 	return ERR_NONE;
 | |
| }
 |