// Copyright 2017 The Periph Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.

package bcm283x

import (
	"errors"
	"fmt"
	"strings"
	"time"

	"periph.io/x/conn/v3/physic"
)

// errClockRegister is returned in a situation where the clock memory is not
// working as expected. It is mocked in tests.
var errClockRegister = errors.New("can't write to clock divisor CPU register")

// Clock sources frequency in hertz.
const (
	clk19dot2MHz = 19200 * physic.KiloHertz
	clk500MHz    = 500 * physic.MegaHertz
)

const (
	// 31:24 password
	clockPasswdCtl clockCtl = 0x5A << 24 // PASSWD
	// 23:11 reserved
	clockMashMask clockCtl = 3 << 9 // MASH
	clockMash0    clockCtl = 0 << 9 // src_freq / divI  (ignores divF)
	clockMash1    clockCtl = 1 << 9
	clockMash2    clockCtl = 2 << 9
	clockMash3    clockCtl = 3 << 9 // will cause higher spread
	clockFlip     clockCtl = 1 << 8 // FLIP
	clockBusy     clockCtl = 1 << 7 // BUSY
	// 6 reserved
	clockKill          clockCtl = 1 << 5   // KILL
	clockEnable        clockCtl = 1 << 4   // ENAB
	clockSrcMask       clockCtl = 0xF << 0 // SRC
	clockSrcGND        clockCtl = 0        // 0Hz
	clockSrc19dot2MHz  clockCtl = 1        // 19.2MHz
	clockSrcTestDebug0 clockCtl = 2        // 0Hz
	clockSrcTestDebug1 clockCtl = 3        // 0Hz
	clockSrcPLLA       clockCtl = 4        // 0Hz
	clockSrcPLLC       clockCtl = 5        // 1000MHz (changes with overclock settings)
	clockSrcPLLD       clockCtl = 6        // 500MHz
	clockSrcHDMI       clockCtl = 7        // 216MHz; may be disabled
	// 8-15 == GND.
)

// clockCtl controls the clock properties.
//
// It must not be changed while busy is set or a glitch may occur.
//
// Page 107
type clockCtl uint32

func (c clockCtl) String() string {
	var out []string
	if c&0xFF000000 == clockPasswdCtl {
		c &^= 0xFF000000
		out = append(out, "PWD")
	}
	switch c & clockMashMask {
	case clockMash1:
		out = append(out, "Mash1")
	case clockMash2:
		out = append(out, "Mash2")
	case clockMash3:
		out = append(out, "Mash3")
	default:
	}
	c &^= clockMashMask
	if c&clockFlip != 0 {
		out = append(out, "Flip")
		c &^= clockFlip
	}
	if c&clockBusy != 0 {
		out = append(out, "Busy")
		c &^= clockBusy
	}
	if c&clockKill != 0 {
		out = append(out, "Kill")
		c &^= clockKill
	}
	if c&clockEnable != 0 {
		out = append(out, "Enable")
		c &^= clockEnable
	}
	switch x := c & clockSrcMask; x {
	case clockSrcGND:
		out = append(out, "GND(0Hz)")
	case clockSrc19dot2MHz:
		out = append(out, "19.2MHz")
	case clockSrcTestDebug0:
		out = append(out, "Debug0(0Hz)")
	case clockSrcTestDebug1:
		out = append(out, "Debug1(0Hz)")
	case clockSrcPLLA:
		out = append(out, "PLLA(0Hz)")
	case clockSrcPLLC:
		out = append(out, "PLLD(1000MHz)")
	case clockSrcPLLD:
		out = append(out, "PLLD(500MHz)")
	case clockSrcHDMI:
		out = append(out, "HDMI(216MHz)")
	default:
		out = append(out, fmt.Sprintf("GND(%d)", x))
	}
	c &^= clockSrcMask
	if c != 0 {
		out = append(out, fmt.Sprintf("clockCtl(0x%0x)", uint32(c)))
	}
	return strings.Join(out, "|")
}

const (
	// 31:24 password
	clockPasswdDiv clockDiv = 0x5A << 24 // PASSWD
	// Integer part of the divisor
	clockDiviShift          = 12
	clockDiviMax            = (1 << 12) - 1
	clockDiviMask  clockDiv = clockDiviMax << clockDiviShift // DIVI
	// Fractional part of the divisor
	clockDivfMask clockDiv = (1 << 12) - 1 // DIVF
)

// clockDiv is a 12.12 fixed point value.
//
// The fractional part generates a significant amount of noise so it is
// preferable to not use it.
//
// Page 108
type clockDiv uint32

func (c clockDiv) String() string {
	i := (c & clockDiviMask) >> clockDiviShift
	c &^= clockDiviMask
	if c == 0 {
		return fmt.Sprintf("%d.0", i)
	}
	return fmt.Sprintf("%d.(%d/%d)", i, c, clockDiviMax)
}

// clock is a pair of clockCtl / clockDiv.
//
// It can be set to one of the sources: clockSrc19dot2MHz(19.2MHz) and
// clockSrcPLLD(500Mhz), then divided to a value to get the resulting clock.
// Per spec the resulting frequency should be under 25Mhz.
type clock struct {
	ctl clockCtl
	div clockDiv
}

// findDivisorExact finds the clock divisor and wait cycles to reduce src to
// desired hz.
//
// The clock divisor is capped to clockDiviMax.
//
// Returns clock divisor, wait cycles. Returns 0, 0 if no exact match is found.
// Favorizes high clock divisor value over high clock wait cycles. This means
// that the function is slower than it could be, but results in more stable
// clock.
func findDivisorExact(src, desired physic.Frequency, maxWaitCycles uint32) (uint32, uint32) {
	if src < desired || src%desired != 0 || src/physic.Frequency(maxWaitCycles*clockDiviMax) > desired {
		// Can't attain without oversampling (too low) or desired frequency is
		// higher than the source (too high) or is not a multiple.
		return 0, 0
	}
	factor := uint32(src / desired)
	// TODO(maruel): Only iterate over valid divisors to save a bit more
	// calculations. Since it's is only doing 32 loops, this is not a big deal.
	for wait := uint32(1); wait <= maxWaitCycles; wait++ {
		if rest := factor % wait; rest != 0 {
			continue
		}
		clk := factor / wait
		if clk == 0 {
			break
		}
		if clk <= clockDiviMax {
			return clk, wait
		}
	}
	return 0, 0
}

// findDivisorOversampled tries to find the lowest allowed oversampling to make
// desiredHz a multiple of srcHz.
//
// Allowed oversampling depends on the desiredHz. Cap oversampling because
// oversampling at 10x in the 1Mhz range becomes unreasonable in term of
// memory usage.
func findDivisorOversampled(src, desired physic.Frequency, maxWaitCycles uint32) (uint32, uint32, physic.Frequency) {
	//log.Printf("findDivisorOversampled(%s, %s, %d)", src, desired, maxWaitCycles)
	// There are 2 reasons:
	// - desired is so low it is not possible to lower src to this frequency
	// - not a multiple, there's a need for a prime number
	// TODO(maruel): Rewrite without a loop, this is not needed. Leverage primes
	// to reduce the number of iterations.
	for multiple := physic.Frequency(2); ; multiple++ {
		n := multiple * desired
		if n > 100*physic.KiloHertz && multiple > 10 {
			break
		}
		if clk, wait := findDivisorExact(src, n, maxWaitCycles); clk != 0 {
			return clk, wait, n
		}
	}
	return 0, 0, 0
}

// calcSource choose the best source to get the exact desired clock.
//
// It calculates the clock source, the clock divisor and the wait cycles, if
// applicable. Wait cycles is 'div minus 1'.
func calcSource(f physic.Frequency, maxWaitCycles uint32) (clockCtl, uint32, uint32, physic.Frequency, error) {
	if f < physic.Hertz {
		return 0, 0, 0, 0, fmt.Errorf("bcm283x-clock: desired frequency %s must be >1hz", f)
	}
	if f > 125*physic.MegaHertz {
		return 0, 0, 0, 0, fmt.Errorf("bcm283x-clock: desired frequency %s is too high", f)
	}
	// http://elinux.org/BCM2835_datasheet_errata states that clockSrc19dot2MHz
	// is the cleanest clock source so try it first.
	div, wait := findDivisorExact(clk19dot2MHz, f, maxWaitCycles)
	if div != 0 {
		return clockSrc19dot2MHz, div, wait, f, nil
	}
	// Try 500Mhz.
	div, wait = findDivisorExact(clk500MHz, f, maxWaitCycles)
	if div != 0 {
		return clockSrcPLLD, div, wait, f, nil
	}

	// Try with up to 10x oversampling. This is generally useful for lower
	// frequencies, below 10kHz. Prefer the one with less oversampling. Only for
	// non-aliased matches.
	div19, wait19, f19 := findDivisorOversampled(clk19dot2MHz, f, maxWaitCycles)
	div500, wait500, f500 := findDivisorOversampled(clk500MHz, f, maxWaitCycles)
	if div19 != 0 && (div500 == 0 || f19 < f500) {
		return clockSrc19dot2MHz, div19, wait19, f19, nil
	}
	if div500 != 0 {
		return clockSrcPLLD, div500, wait500, f500, nil
	}
	return 0, 0, 0, 0, errors.New("failed to find a good clock")
}

// set changes the clock frequency to the desired value or the closest one
// otherwise.
//
// f=0 means disabled.
//
// maxWaitCycles is the maximum oversampling via an additional wait cycles that
// can further divide the clock. Use 1 if no additional wait cycle is
// available. It is expected to be dmaWaitcyclesMax+1.
//
// Returns the actual clock used and divisor.
func (c *clock) set(f physic.Frequency, maxWaitCycles uint32) (physic.Frequency, uint32, error) {
	if f == 0 {
		c.ctl = clockPasswdCtl | clockKill
		for c.ctl&clockBusy != 0 {
		}
		return 0, 0, nil
	}
	ctl, div, div2, actual, err := calcSource(f, maxWaitCycles)
	if err != nil {
		return 0, 0, err
	}
	return actual, div2, c.setRaw(ctl, div)
}

// setRaw sets the clock speed with the clock source and the divisor.
func (c *clock) setRaw(ctl clockCtl, div uint32) error {
	if div < 1 || div > clockDiviMax {
		return errors.New("invalid clock divisor")
	}
	if ctl != clockSrc19dot2MHz && ctl != clockSrcPLLD {
		return errors.New("invalid clock control")
	}
	// Stop the clock.
	// TODO(maruel): Do not stop the clock if the current clock rate is the one
	// desired.
	for c.ctl&clockBusy != 0 {
		c.ctl = clockPasswdCtl | clockKill
	}
	d := clockDiv(div << clockDiviShift)
	c.div = clockPasswdDiv | d
	Nanospin(10 * time.Nanosecond)
	// Page 107
	c.ctl = clockPasswdCtl | ctl
	Nanospin(10 * time.Nanosecond)
	c.ctl = clockPasswdCtl | ctl | clockEnable
	if c.div != d {
		// This error is mocked out in tests, so the code path of set() callers can
		// follow on.
		return errClockRegister
	}
	return nil
}

func (c *clock) String() string {
	return fmt.Sprintf("%s / %s", c.ctl, c.div)
}

// clockMap is the memory mapped clock registers.
//
// The clock #1 must not be touched since it is being used by the ethernet
// controller.
//
// Page 107 for gp0~gp2.
// https://scribd.com/doc/127599939/BCM2835-Audio-clocks for PCM/PWM.
type clockMap struct {
	reserved0 [0x70 / 4]uint32          //
	gp0       clock                     // CM_GP0CTL+CM_GP0DIV; 0x70-0x74 (125MHz max)
	gp1ctl    uint32                    // CM_GP1CTL+CM_GP1DIV; 0x78-0x7A must not use (used by ethernet)
	gp1div    uint32                    // CM_GP1CTL+CM_GP1DIV; 0x78-0x7A must not use (used by ethernet)
	gp2       clock                     // CM_GP2CTL+CM_GP2DIV; 0x80-0x84 (125MHz max)
	reserved1 [(0x98 - 0x88) / 4]uint32 // 0x88-0x94
	pcm       clock                     // CM_PCMCTL+CM_PCMDIV 0x98-0x9C
	pwm       clock                     // CM_PWMCTL+CM_PWMDIV 0xA0-0xA4
}

func (c *clockMap) GoString() string {
	return fmt.Sprintf(
		"{\n  gp0: %s,\n  gp1: %s,\n  gp2: %s,\n  pcm: %sw,\n  pwm: %s,\n}",
		&c.gp0, &clock{clockCtl(c.gp1ctl), clockDiv(c.gp1div)}, &c.gp2, &c.pcm, &c.pwm)
}
