##########################################################################
# "THE ANY BEVERAGE-WARE LICENSE" (Revision 42 - based on beer-ware
# license):
# <dev@layer128.net> wrote this file. As long as you retain this notice
# you can do whatever you want with this stuff. If we meet some day, and
# you think this stuff is worth it, you can buy me a be(ve)er(age) in
# return. (I don't like beer much.)
#
# Matthias Kleemann
#
# Modified by multiplemonomials for LoseAVR
##########################################################################

##########################################################################
# The toolchain requires some variables set.
#
# AVR_MCU (default: atmega8)
#     the type of AVR the application is built for
# AVR_L_FUSE (NO DEFAULT)
#     the LOW fuse value for the MCU used
# AVR_H_FUSE (NO DEFAULT)
#     the HIGH fuse value for the MCU used
# MCU_SPEED (NO DEFAULT)
#     speed of the processor, in Hz
# AVR_UPLOAD_TOOL (default: avrdude)
#     the application used to upload to the MCU
#     NOTE: The toolchain is currently quite specific about
#           the commands used, so it needs tweaking.
# AVR_UPLOAD_PORT (default: usb)
#     the port used for the upload tool, e.g. usb
# AVR_PROGRAMMER (default: avrispmkII)
#     the programmer hardware used, e.g. avrispmkII
##########################################################################

##################################################################################
# some cmake cross-compile necessities
##################################################################################

# Find toolchain.
# Check if user passed us the location
# NOTE: we can't have AVR_FIND_ROOT_PATH be a CMake variable because then all of the temporary projects for compile checks can't see it.
if(DEFINED ENV{AVR_FIND_ROOT_PATH})
    set(CMAKE_FIND_ROOT_PATH $ENV{AVR_FIND_ROOT_PATH})
else()
	# set relative to toolchain file location
	get_filename_component(CMAKE_FIND_ROOT_PATH "${CMAKE_CURRENT_LIST_DIR}/../.." REALPATH)
endif()

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

# not added automatically, since CMAKE_SYSTEM_NAME is "generic"
set(CMAKE_SYSTEM_INCLUDE_PATH "${CMAKE_FIND_ROOT_PATH}/include")
set(CMAKE_SYSTEM_LIBRARY_PATH "${CMAKE_FIND_ROOT_PATH}/lib")

##################################################################################
# status messages for generating
##################################################################################
message(STATUS "AVR toolchain root: ${CMAKE_FIND_ROOT_PATH}")
message(STATUS "AVR toolchain include path: ${CMAKE_SYSTEM_INCLUDE_PATH}")
message(STATUS "AVR toolchain library path: ${CMAKE_SYSTEM_LIBRARY_PATH}")

##################################################################################
# compiler options for all build types
##################################################################################
add_compile_options(-fpack-struct -fshort-enums -funsigned-char -funsigned-bitfields -ffunction-sections -Wall)

# http://gcc.gnu.org/onlinedocs/gcc-4.8.2/gcc/Alternate-Keywords.html#Alternate-Keywords
# [...]-pedantic and other options cause warnings for many GNU C extensions. You can prevent such warnings within
# one expression by writing __extension__ before the expression. __extension__ has no effect aside from this.[...] 
# add_compile_options("-pedantic")

##########################################################################
# executables in use
##########################################################################
find_program(CMAKE_C_COMPILER avr-gcc)
find_program(CMAKE_CXX_COMPILER avr-g++)

find_program(AVR_OBJCOPY avr-objcopy)
find_program(AVR_SIZE_TOOL avr-size)
find_program(AVR_OBJDUMP avr-objdump)

##########################################################################
# toolchain starts with defining mandatory variables
##########################################################################
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR avr)

##########################################################################
# some necessary tools and variables for AVR builds, which may not
# defined yet
# - AVR_UPLOAD_TOOL
# - AVR_UPLOAD_PORT
# - AVR_PROGRAMMER
# - AVR_MCU
# - AVR_SIZE_ARGS
##########################################################################

# default upload tool
find_program(AVR_UPLOAD_TOOL avrdude DOC "Path to AVR upload tool (AVRDUDE)")

# default upload tool port
set(AVR_UPLOAD_PORT usb CACHE STRING "Upload port for AVRDUDE.  Should be either 'usb', or a com port name like 'COM3'")

# default programmer (hardware)
set(AVR_PROGRAMMER avrispmkII CACHE STRING "Programmer to use when uploading (-c option to AVRDUDE)")

# default MCU (chip)
set(AVR_MCU atmega8 CACHE STRING "AVR MCU to build for (see 'avr-gcc --target-help' for valid values)")

#default avr-size args
set(AVR_SIZE_ARGS -B)

##########################################################################
# check build types:
# - Debug
# - Release
# - RelWithDebInfo
#
# Release is chosen, because of some optimized functions in the
# AVR toolchain, e.g. _delay_ms().
##########################################################################
set(CMAKE_CONFIGURATION_TYPES Debug Release RelWithDebInfo MinSizeRel)

# provide a default build type
set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Configuration to build.  Allowed values: Debug Release RelWithDebInfo MinSizeRel")

if(NOT ((CMAKE_BUILD_TYPE MATCHES Release) OR
		  (CMAKE_BUILD_TYPE MATCHES RelWithDebInfo) OR
		  (CMAKE_BUILD_TYPE MATCHES Debug) OR
		  (CMAKE_BUILD_TYPE MATCHES MinSizeRel)))
	
	message(WARNING "CMAKE_BUILD_TYPE was set to \"${CMAKE_BUILD_TYPE}\", which is not a valid value.  It has been reset to \"Release\"") 
		  
	set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE)
	
endif()
			  
			  
##################################################################################
# set compiler options for build types
##################################################################################

if(CMAKE_BUILD_TYPE MATCHES Release OR CMAKE_BUILD_TYPE MATCHES MinSizeRel)
   set(CMAKE_C_FLAGS_RELEASE "-Os")
   set(CMAKE_CXX_FLAGS_RELEASE "-Os")
endif()

if(CMAKE_BUILD_TYPE MATCHES RelWithDebInfo)
   set(CMAKE_C_FLAGS_RELWITHDEBINFO "-Os -save-temps -g -gdwarf-3 -gstrict-dwarf")
   set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-Os -save-temps -g -gdwarf-3 -gstrict-dwarf")
endif()

if(CMAKE_BUILD_TYPE MATCHES Debug)
   set(CMAKE_C_FLAGS_DEBUG "-O0 -save-temps -g -gdwarf-3 -gstrict-dwarf")
   set(CMAKE_CXX_FLAGS_DEBUG "-O0 -save-temps -g -gdwarf-3 -gstrict-dwarf")
endif()

##########################################################################
# add_avr_executable
# - IN_VAR: EXECUTABLE_NAME
#
# Creates targets and dependencies for AVR toolchain, building an
# executable. Calls add_executable with ELF file as target name, so
# any link dependencies need to be using that target, like:
# target_link_libraries(<EXECUTABLE_NAME>.elf ...).
##########################################################################
function(add_avr_executable EXECUTABLE_NAME)
	if(NOT ARGN)
		message(FATAL_ERROR "No source files given for ${EXECUTABLE_NAME}.")
	endif(NOT ARGN)

	# set file names
	set(elf_file ${EXECUTABLE_NAME}.elf)
	set(hex_file ${EXECUTABLE_NAME}.hex)
	set(map_file ${EXECUTABLE_NAME}.map)
	set(eeprom_image ${EXECUTABLE_NAME}-eeprom.hex)

	# elf file
	add_executable(${elf_file} ${ARGN})
	
	# set compile and link flags
	set_property(TARGET ${elf_file} APPEND PROPERTY COMPILE_OPTIONS -mmcu=${AVR_MCU} -DF_CPU=${MCU_SPEED}UL)
	set_property(TARGET ${elf_file} APPEND PROPERTY LINK_LIBRARIES -Wl,--gc-sections -mrelax -Wl,-Map,${map_file})

	# objcopy to hex
	add_custom_command(
		OUTPUT ${hex_file}
		COMMAND
			${AVR_OBJCOPY} -j .text -j .data -O ihex ${elf_file} ${hex_file}
		COMMAND
			${AVR_SIZE_TOOL} ${AVR_SIZE_ARGS} ${elf_file}
		DEPENDS ${elf_file}
	)

	# eeprom
	add_custom_command(
		OUTPUT ${eeprom_image}
		COMMAND
			${AVR_OBJCOPY} -j .eeprom --set-section-flags=.eeprom=alloc,load
				--change-section-lma .eeprom=0 --no-change-warnings
				-O ihex ${elf_file} ${eeprom_image}
		DEPENDS ${elf_file}
	)

	add_custom_target(
		${EXECUTABLE_NAME}
		ALL
		DEPENDS ${hex_file} ${eeprom_image}
	)

	set_target_properties(
		${EXECUTABLE_NAME}
		PROPERTIES
			OUTPUT_NAME "${elf_file}"
	)

	# clean
	get_directory_property(clean_files ADDITIONAL_MAKE_CLEAN_FILES)
	set_directory_properties(
		PROPERTIES
			ADDITIONAL_MAKE_CLEAN_FILES "${map_file}"
	)

	# upload - with avrdude
	add_custom_target(
		upload_${EXECUTABLE_NAME}
		${AVR_UPLOAD_TOOL} -v -p ${AVR_MCU} -c ${AVR_PROGRAMMER} ${AVR_UPLOADTOOL_OPTIONS}
			-U flash:w:${hex_file}:i
			-P ${AVR_UPLOAD_PORT}
		DEPENDS ${hex_file}
		COMMENT "Uploading ${hex_file} to ${AVR_MCU} using ${AVR_PROGRAMMER}"
	)

	# upload eeprom only - with avrdude
	# see also bug http://savannah.nongnu.org/bugs/?40142
	add_custom_target(
		upload_eeprom_${EXECUTABLE_NAME}
		${AVR_UPLOAD_TOOL} -v -p ${AVR_MCU} -c ${AVR_PROGRAMMER} ${AVR_UPLOADTOOL_OPTIONS}
			-U eeprom:w:${eeprom_image}
			-P ${AVR_UPLOAD_PORT}
		DEPENDS ${eeprom_image}
		COMMENT "Uploading ${eeprom_image} to ${AVR_MCU} using ${AVR_PROGRAMMER}"
	)
	
		
	#disassemble
	add_custom_target(
		disassemble_${EXECUTABLE_NAME}
		${AVR_OBJDUMP} -h -S ${elf_file} > ${EXECUTABLE_NAME}.lst
		DEPENDS ${elf_file}
		COMMENT "Disassembling elf file to ${EXECUTABLE_NAME}.lst"
	)


	##################################################################################
	# global targets, that only need to be added once.
	# we wait to define these until the first avr_add_executable call,
	# so that the user can set AVR_MCU and the other variables from
	# their CMakeLists if they want
	##################################################################################
	
	if(NOT TARGET get_status)
		
		#get status
		add_custom_target(
			get_status
			${AVR_UPLOAD_TOOL} -v -p ${AVR_MCU} -c ${AVR_PROGRAMMER} -P ${AVR_UPLOAD_PORT} -n -v
			COMMENT "Get status from ${AVR_MCU}"
		)
		
		#get fuses
		add_custom_target(
			get_fuses
			${AVR_UPLOAD_TOOL} -v -p ${AVR_MCU} -c ${AVR_PROGRAMMER} -P ${AVR_UPLOAD_PORT} -n
				-U lfuse:r:-:b
				-U hfuse:r:-:b
			COMMENT "Get fuses from ${AVR_MCU}:"
		)

		if(DEFINED AVR_L_FUSE AND AVR_H_FUSE)

			#set fuses
			add_custom_target(
				set_fuses
				${AVR_UPLOAD_TOOL} -v -p ${AVR_MCU} -c ${AVR_PROGRAMMER} -P ${AVR_UPLOAD_PORT}
					-U lfuse:w:${AVR_L_FUSE}:m
					-U hfuse:w:${AVR_H_FUSE}:m
					COMMENT "Setup: High Fuse: ${AVR_H_FUSE} Low Fuse: ${AVR_L_FUSE}"
			)
		else()
			
			#print error message
			add_custom_target(
				set_fuses
				COMMAND echo "Error: you must define the AVR_L_FUSE and AVR_H_FUSE variables in CMake to use this target."
				COMMAND errorerrorerrorerrorerror # guaranteed to return an error, on any shell
			)
		endif()
			
		#get oscillator calibration
		add_custom_target(
			get_calibration
				${AVR_UPLOAD_TOOL} -v -p ${AVR_MCU} -c ${AVR_PROGRAMMER} -P ${AVR_UPLOAD_PORT}
				-U calibration:r:${AVR_MCU}_calib.tmp:r
				COMMENT "Write calibration status of internal oscillator to ${AVR_MCU}_calib.tmp."
		)

		#set oscillator calibration
		add_custom_target(
			set_calibration
			${AVR_UPLOAD_TOOL} -p ${AVR_MCU} -c ${AVR_PROGRAMMER} -P ${AVR_UPLOAD_PORT}
				-U calibration:w:${AVR_MCU}_calib.hex
				COMMENT "Program calibration status of internal oscillator from ${AVR_MCU}_calib.hex."
		)
	endif()
endfunction(add_avr_executable)

##########################################################################
# add_avr_library
# - IN_VAR: LIBRARY_NAME
#
# Creates an AVR static library with the given name.
# 
##########################################################################
function(add_avr_library LIBRARY_NAME)
	if(NOT ARGN)
		message(FATAL_ERROR "No source files given for ${LIBRARY_NAME}.")
	endif(NOT ARGN)

	set(lib_file ${LIBRARY_NAME})

	add_library(${LIBRARY_NAME} STATIC ${ARGN})

	set_property(TARGET ${LIBRARY_NAME} APPEND PROPERTY COMPILE_OPTIONS -mmcu=${AVR_MCU} -DF_CPU=${MCU_SPEED}UL)
endfunction(add_avr_library)