-module(pmod_dio). -behaviour(gen_server). % FIXME: remove! -export([crc5/1]). -export([read/2]). -export([read_burst/2]). -export([write/3]). % API -export([start_link/2]). % Callbacks -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). % TODO: Verify! -define(SPI_MODE, #{clock => {low, leading}}). -define(CRC5_START, 16#1F). -define(CRC5_POLY, 16#15). -include("grisp.hrl"). %--- API ----------------------------------------------------------------------- start_link(Connector, _Opts) -> gen_server:start_link({local, ?MODULE}, ?MODULE, Connector, []). read(Chip, Reg) -> Value = <<0>>, call({request, {Chip, single, read, Reg, Value}}). read_burst(Chip, 'DoiLevel') -> Value = <<0, 0, 0, 0, 0, 0>>, call({request, {Chip, burst, read, 'DoiLevel', Value}}); read_burst(_Chip, Reg) -> error({invalid_burst_register, Reg}). write(Chip, Reg, Value) -> call({request, {Chip, single, write, Reg, Value}}). %--- Callbacks ----------------------------------------------------------------- init(Slot) -> grisp_devices:register(Slot, ?MODULE), Bus = grisp_spi:open(Slot), {ok, #{bus => Bus}}. handle_call({request, Request}, _From, #{bus := Bus} = State) -> {reply, send_request(Bus, Request), State}; handle_call(Request, From, _State) -> error({unknown_call, Request, From}). handle_cast(Request, _State) -> error({unknown_cast, Request}). %--- Internal ------------------------------------------------------------------ call(Call) -> Dev = grisp_devices:default(?MODULE), gen_server:call(Dev#device.pid, Call). send_request(Bus, {Chip, Type, Op, Reg, Value}) -> Addr = Chip - 1, Encoded = encode_request(Reg, Value), Request = request(Type, Op, Addr, Reg, Encoded), [Response] = grisp_spi:transfer(Bus, [{?SPI_MODE, Request}]), decode_response(Op, Addr, Reg, bit_size(Encoded), Response). request(Type, Op, Addr, Reg, Value) -> Req = <>, CRC = crc5(Req), <>. type(single) -> 0; type(burst) -> 1. rw(read) -> 0; rw(write) -> 1. crc5(Bitstring) -> crc5(Bitstring, ?CRC5_START). crc5(<>, R) -> R2 = case (Bit band 16#01) bxor ((R band 16#10) bsr 4) of Val when Val > 0 -> ?CRC5_POLY bxor (R bsl 1) band 16#1f; _Else -> (R bsl 1) band 16#1f end, crc5(Bin, R2); crc5(<<>>, R) -> R. encode_request(_Reg, Value) when is_binary(Value) -> Value; encode_request(Reg, Value) -> reg(encode, Reg, Value). decode_response(Op, Addr, Reg, Len, <<_:2, Response/bitstring>>) -> 0 = crc5(Response), << SHTVDD:1, AbvVDD:1, OWOffF:1, OvrCurr:1, OvlDf:1, GLOBLF:1, Result:Len/bitstring, Addr:2, ThrErr:1, _CRC:5 >> = Response, maps:merge( #{ 'SHTVDD' => boolean(SHTVDD), 'AbvVDD' => boolean(AbvVDD), 'OWOffF' => boolean(OWOffF), 'OvrCurr' => boolean(OvrCurr), 'OvlDf' => boolean(OvlDf), 'GLOBLF' => boolean(GLOBLF), 'ThrErr' => boolean(ThrErr) }, decode_result(Op, Reg, Result) ). decode_result(read, Reg, Result) -> #{ result => decode_registers(Reg, Result) }; decode_result(write, _Reg, Result) -> << DiLvl4:1, DiLvl3:1, DiLvl2:1, DiLvl1:1, F4:1, F3:1, F2:1, F1:1 >> = Result, #{ result => #{ 'DiLvl4' => boolean(DiLvl4), 'DiLvl3' => boolean(DiLvl3), 'DiLvl2' => boolean(DiLvl2), 'DiLvl1' => boolean(DiLvl1), 'F4' => boolean(F4), 'F3' => boolean(F3), 'F2' => boolean(F2), 'F1' => boolean(F1) } }. decode_registers(StartReg, Response) -> decode_registers(reg(StartReg), Response, #{}). decode_registers(Reg, <>, Result) -> Name = reg(Reg), decode_registers(Reg + 1, Response, Result#{ Name => reg(decode, Name, Resp) }); decode_registers(_Reg, <<>>, Result) -> Result. -define(MAP_FIELD(Name), list_to_atom(??Name) => field(decode, list_to_atom(??Name), Name) ). -define(BIN_FIELD(Name), (field(encode, list_to_atom(??Name), Name)) ). reg('SetOUT') -> 16#00; reg('SetLED') -> 16#01; reg('DoiLevel') -> 16#02; reg('Interrupt') -> 16#03; reg('OvrLdChF') -> 16#04; reg('OpnWirChF') -> 16#05; reg('ShtVDDChF') -> 16#06; reg('GlobalErr') -> 16#07; reg('OpnWrEn') -> 16#08; reg('ShtVDDEn') -> 16#09; reg('Config1') -> 16#0A; reg('Config2') -> 16#0B; reg('ConfigDI') -> 16#0C; reg('ConfigDO') -> 16#0D; reg('CurrLim') -> 16#0E; reg('Mask') -> 16#0F; reg(16#00) -> 'SetOUT'; reg(16#01) -> 'SetLED'; reg(16#02) -> 'DoiLevel'; reg(16#03) -> 'Interrupt'; reg(16#04) -> 'OvrLdChF'; reg(16#05) -> 'OpnWirChF'; reg(16#06) -> 'ShtVDDChF'; reg(16#07) -> 'GlobalErr'; reg(16#08) -> 'OpnWrEn'; reg(16#09) -> 'ShtVDDEn'; reg(16#0A) -> 'Config1'; reg(16#0B) -> 'Config2'; reg(16#0C) -> 'ConfigDI'; reg(16#0D) -> 'ConfigDO'; reg(16#0E) -> 'CurrLim'; reg(16#0F) -> 'Mask'; reg(Reg) -> error({invalid_register, Reg}). reg(decode, 'SetOUT', Content) when is_binary(Content) -> << SetDi4:1, SetDi3:1, SetDi2:1, SetDi1:1, HighO4:1, HighO3:1, HighO2:1, HighO1:1 >> = Content, #{ ?MAP_FIELD(SetDi4), ?MAP_FIELD(SetDi3), ?MAP_FIELD(SetDi2), ?MAP_FIELD(SetDi1), ?MAP_FIELD(HighO4), ?MAP_FIELD(HighO3), ?MAP_FIELD(HighO2), ?MAP_FIELD(HighO1) }; reg(encode, 'SetOUT', Content) when is_map(Content) -> #{ 'SetDi4' := SetDi4, 'SetDi3' := SetDi3, 'SetDi2' := SetDi2, 'SetDi1' := SetDi1, 'HighO4' := HighO4, 'HighO3' := HighO3, 'HighO2' := HighO2, 'HighO1' := HighO1 } = Content, << ?BIN_FIELD(SetDi4):1, ?BIN_FIELD(SetDi3):1, ?BIN_FIELD(SetDi2):1, ?BIN_FIELD(SetDi1):1, ?BIN_FIELD(HighO4):1, ?BIN_FIELD(HighO3):1, ?BIN_FIELD(HighO2):1, ?BIN_FIELD(HighO1):1 >>; reg(decode, 'SetLED', Content) when is_binary(Content) -> << SLED4:1, SLED3:1, SLED2:1, SLED1:1, FLED4:1, FLED3:1, FLED2:1, FLED1:1 >> = Content, #{ ?MAP_FIELD(SLED4), ?MAP_FIELD(SLED3), ?MAP_FIELD(SLED2), ?MAP_FIELD(SLED1), ?MAP_FIELD(FLED4), ?MAP_FIELD(FLED3), ?MAP_FIELD(FLED2), ?MAP_FIELD(FLED1) }; reg(encode, 'SetLED', Content) when is_map(Content) -> #{ 'SLED4' := SLED4, 'SLED3' := SLED3, 'SLED2' := SLED2, 'SLED1' := SLED1, 'FLED4' := FLED4, 'FLED3' := FLED3, 'FLED2' := FLED2, 'FLED1' := FLED1 } = Content, << ?BIN_FIELD(SLED4):1, ?BIN_FIELD(SLED3):1, ?BIN_FIELD(SLED2):1, ?BIN_FIELD(SLED1):1, ?BIN_FIELD(FLED4):1, ?BIN_FIELD(FLED3):1, ?BIN_FIELD(FLED2):1, ?BIN_FIELD(FLED1):1 >>; reg(decode, 'DoiLevel', Content) when is_binary(Content) -> << SafeDemagF4:1, SafeDemagF3:1, SafeDemagF2:1, SafeDemagF1:1, DoiLevel4_VDDOKFault:1, DoiLevel3_VDDOKFault:1, DoiLevel2_VDDOKFault:1, DoiLevel1_VDDOKFault:1 >> = Content, #{ ?MAP_FIELD(SafeDemagF4), ?MAP_FIELD(SafeDemagF3), ?MAP_FIELD(SafeDemagF2), ?MAP_FIELD(SafeDemagF1), ?MAP_FIELD(DoiLevel4_VDDOKFault), ?MAP_FIELD(DoiLevel3_VDDOKFault), ?MAP_FIELD(DoiLevel2_VDDOKFault), ?MAP_FIELD(DoiLevel1_VDDOKFault) }; reg(decode, 'Interrupt', Content) -> <> = Content, #{ ?MAP_FIELD(ComErr), ?MAP_FIELD(SupplyErr), ?MAP_FIELD(DeMagFault), ?MAP_FIELD(ShtVDDFault), ?MAP_FIELD(AboveVDDFault), ?MAP_FIELD(OWOffFault), ?MAP_FIELD(CurrLim), ?MAP_FIELD(OverLdFault) }; reg(decode, 'OvrLdChF', Content) -> << CL4:1, CL3:1, CL2:1, CL1:1, OVL4:1, OVL3:1, OVL2:1, OVL1:1 >> = Content, #{ ?MAP_FIELD(CL4), ?MAP_FIELD(CL3), ?MAP_FIELD(CL2), ?MAP_FIELD(CL1), ?MAP_FIELD(OVL4), ?MAP_FIELD(OVL3), ?MAP_FIELD(OVL2), ?MAP_FIELD(OVL1) }; reg(decode, 'OpnWirChF', Content) -> << AboveVDD4:1, AboveVDD3:1, AboveVDD2:1, AboveVDD1:1, OWOff4:1, OWOff3:1, OWOff2:1, OWOff1:1 >> = Content, #{ ?MAP_FIELD(AboveVDD4), ?MAP_FIELD(AboveVDD3), ?MAP_FIELD(AboveVDD2), ?MAP_FIELD(AboveVDD1), ?MAP_FIELD(OWOff4), ?MAP_FIELD(OWOff3), ?MAP_FIELD(OWOff2), ?MAP_FIELD(OWOff1) }; reg(decode, 'ShtVDDChF', Content) -> << VDDOV4:1, VDDOV3:1, VDDOV2:1, VDDOV1:1, SHVDD4:1, SHVDD3:1, SHVDD2:1, SHVDD1:1 >> = Content, #{ ?MAP_FIELD(VDDOV4), ?MAP_FIELD(VDDOV3), ?MAP_FIELD(VDDOV2), ?MAP_FIELD(VDDOV1), ?MAP_FIELD(SHVDD4), ?MAP_FIELD(SHVDD3), ?MAP_FIELD(SHVDD2), ?MAP_FIELD(SHVDD1) }; reg(decode, 'GlobalErr', Content) -> << WDogErr:1, LossGND:1, ThrmShutd:1, VDD_UVLO:1, VDD_Warn:1, VDD_Low:1, V5_UVLO:1, VINT_UV:1 >> = Content, #{ ?MAP_FIELD(WDogErr), ?MAP_FIELD(LossGND), ?MAP_FIELD(ThrmShutd), ?MAP_FIELD(VDD_UVLO), ?MAP_FIELD(VDD_Warn), ?MAP_FIELD(VDD_Low), ?MAP_FIELD(V5_UVLO), ?MAP_FIELD(VINT_UV) }; reg(decode, 'Config1', Content) when is_binary(Content) -> << LedCurrLim:1, FLatchEn:1, FilterLong:1, FFilterEn:1, FLEDStretch:2, SLEDSet:1, FLEDSet:1 >> = Content, #{ ?MAP_FIELD(LedCurrLim), ?MAP_FIELD(FLatchEn), ?MAP_FIELD(FilterLong), ?MAP_FIELD(FFilterEn), ?MAP_FIELD(FLEDStretch), ?MAP_FIELD(SLEDSet), ?MAP_FIELD(FLEDSet) }; reg(encode, 'Config1', Content) when is_map(Content) -> #{ 'LedCurrLim' := LedCurrLim, 'FLatchEn' := FLatchEn, 'FilterLong' := FilterLong, 'FFilterEn' := FFilterEn, 'FLEDStretch' := FLEDStretch, 'SLEDSet' := SLEDSet, 'FLEDSet' := FLEDSet } = Content, << ?BIN_FIELD(LedCurrLim):1, ?BIN_FIELD(FLatchEn):1, ?BIN_FIELD(FilterLong):1, ?BIN_FIELD(FFilterEn):1, ?BIN_FIELD(FLEDStretch):2, ?BIN_FIELD(SLEDSet):1, ?BIN_FIELD(FLEDSet):1 >>; reg(decode, 'Config2', Content) when is_binary(Content) -> << WDTo:2, OWOffCs:2, ShtVddThr:2, SynchWDEn:1, VDDOnThr:1 >> = Content, #{ ?MAP_FIELD(WDTo), ?MAP_FIELD(OWOffCs), ?MAP_FIELD(ShtVddThr), ?MAP_FIELD(SynchWDEn), ?MAP_FIELD(VDDOnThr) }; reg(encode, 'Config2', Content) when is_map(Content) -> #{ 'WDTo' := WDTo, 'OWOffCs' := OWOffCs, 'ShtVddThr' := ShtVddThr, 'SynchWDEn' := SynchWDEn, 'VDDOnThr' := VDDOnThr } = Content, << ?BIN_FIELD(WDTo):2, ?BIN_FIELD(OWOffCs):2, ?BIN_FIELD(ShtVddThr):2, ?BIN_FIELD(SynchWDEn):1, ?BIN_FIELD(VDDOnThr):1 >>; reg(decode, 'ConfigDI', Content) when is_binary(Content) -> << Typ2Di:1, % Reserved 0:1, VDDFaultDis:1, VDDFaultSel:1, AboveVDDProtEn:1, OVLStretchEn:1, OVLBlank:2 >> = Content, #{ ?MAP_FIELD(Typ2Di), ?MAP_FIELD(VDDFaultDis), ?MAP_FIELD(VDDFaultSel), ?MAP_FIELD(AboveVDDProtEn), ?MAP_FIELD(OVLStretchEn), ?MAP_FIELD(OVLBlank) }; reg(encode, 'ConfigDI', Content) when is_map(Content) -> #{ 'Typ2Di' := Typ2Di, 'VDDFaultDis' := VDDFaultDis, 'VDDFaultSel' := VDDFaultSel, 'AboveVDDProtEn' := AboveVDDProtEn, 'OVLStretchEn' := OVLStretchEn, 'OVLBlank' := OVLBlank } = Content, << ?BIN_FIELD(Typ2Di):1, % Reserved 0:1, ?BIN_FIELD(VDDFaultDis):1, ?BIN_FIELD(VDDFaultSel):1, ?BIN_FIELD(AboveVDDProtEn):1, ?BIN_FIELD(OVLStretchEn):1, ?BIN_FIELD(OVLBlank):2 >>; reg(decode, 'ConfigDO', Content) when is_binary(Content) -> << DoMode4:2, DoMode3:2, DoMode2:2, DoMode1:2 >> = Content, #{ ?MAP_FIELD(DoMode4), ?MAP_FIELD(DoMode3), ?MAP_FIELD(DoMode2), ?MAP_FIELD(DoMode1) }; reg(encode, 'ConfigDO', Content) when is_map(Content) -> #{ 'DoMode4' := DoMode4, 'DoMode3' := DoMode3, 'DoMode2' := DoMode2, 'DoMode1' := DoMode1 } = Content, << ?BIN_FIELD(DoMode4):2, ?BIN_FIELD(DoMode3):2, ?BIN_FIELD(DoMode2):2, ?BIN_FIELD(DoMode1):2 >>; reg(decode, 'CurrLim', Content) when is_binary(Content) -> << CL4:2, CL3:2, CL2:2, CL1:2 >> = Content, #{ ?MAP_FIELD(CL4), ?MAP_FIELD(CL3), ?MAP_FIELD(CL2), ?MAP_FIELD(CL1) }; reg(encode, 'CurrLim', Content) when is_map(Content) -> #{ 'CL4' := CL4, 'CL3' := CL3, 'CL2' := CL2, 'CL1' := CL1 } = Content, << ?BIN_FIELD(CL4):2, ?BIN_FIELD(CL3):2, ?BIN_FIELD(CL2):2, ?BIN_FIELD(CL1):2 >>; reg(decode, 'Mask', Content) when is_binary(Content) -> << CommErrM:1, SupplyErrM:1, VddOKM:1, ShtVddM:1, AboveVDDM:1, OWOffM:1, CurrLimM:1, OverLdM:1 >> = Content, #{ ?MAP_FIELD(CommErrM), ?MAP_FIELD(SupplyErrM), ?MAP_FIELD(VddOKM), ?MAP_FIELD(ShtVddM), ?MAP_FIELD(AboveVDDM), ?MAP_FIELD(OWOffM), ?MAP_FIELD(CurrLimM), ?MAP_FIELD(OverLdM) }; reg(encode, 'Mask', Content) when is_map(Content) -> #{ 'CommErrM' := CommErrM, 'SupplyErrM' := SupplyErrM, 'VddOKM' := VddOKM, 'ShtVddM' := ShtVddM, 'AboveVDDM' := AboveVDDM, 'OWOffM' := OWOffM, 'CurrLimM' := CurrLimM, 'OverLdM' := OverLdM } = Content, << ?BIN_FIELD(CommErrM):1, ?BIN_FIELD(SupplyErrM):1, ?BIN_FIELD(VddOKM):1, ?BIN_FIELD(ShtVddM):1, ?BIN_FIELD(AboveVDDM):1, ?BIN_FIELD(OWOffM):1, ?BIN_FIELD(CurrLimM):1, ?BIN_FIELD(OverLdM):1 >>; reg(decode, _Reg, Content) -> Content. field(decode, 'FLEDStretch', Value) -> pick(Value, { false, {millisecond, 1_000}, {millisecond, 2_000}, {millisecond, 3_000} }); field(encode, 'FLEDStretch', false) -> 0; field(encode, 'FLEDStretch', {millisecond, 1_000}) -> 1; field(encode, 'FLEDStretch', {millisecond, 2_000}) -> 2; field(encode, 'FLEDStretch', {millisecond, 3_000}) -> 3; field(decode, 'WDTo', Value) -> pick(Value, { false, {millisecond, 200}, {millisecond, 600}, {millisecond, 1_200} }); field(encode, 'WDTo', false) -> 0; field(encode, 'WDTo', {millisecond, 200}) -> 1; field(encode, 'WDTo', {millisecond, 600}) -> 2; field(encode, 'WDTo', {millisecond, 1_200}) -> 3; field(decode, 'OWOffCs', Value) -> {microampere, pick(Value, {60, 100, 300, 600})}; field(encode, 'OWOffCs', {microampere, 60}) -> 0; field(encode, 'OWOffCs', {microampere, 100}) -> 1; field(encode, 'OWOffCs', {microampere, 300}) -> 2; field(encode, 'OWOffCs', {micro_apmere, 600}) -> 3; field(decode, 'ShtVDDThr', Value) -> {volt, pick(Value, {9, 10, 12, 14})}; field(encode, 'ShtVDDThr', {volt, 9}) -> 0; field(encode, 'ShtVDDThr', {volt, 10}) -> 1; field(encode, 'ShtVDDThr', {volt, 12}) -> 2; field(encode, 'ShtVDDThr', {volt, 14}) -> 3; field(decode, 'Typ2Di', 0) -> type_1_3; field(decode, 'Typ2Di', 1) -> type_2; field(encode, 'Typ2Di', type_1_3) -> 0; field(encode, 'Typ2Di', type_2) -> 1; field(decode, 'OVLBlack', Value) -> pick(Value, { false, {millisecond, 8}, {millisecond, 50}, {millisecond, 300} }); field(encode, 'OVLBlack', false) -> 0; field(encode, 'OVLBlack', {millisecond, 8}) -> 1; field(encode, 'OVLBlack', {millisecond, 50}) -> 2; field(encode, 'OVLBlack', {millisecond, 300}) -> 3; field(decode, Reg, Value) when Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1' -> pick(Value, { high_side, high_side_2x, active_clamp_push_pull, simple_push_pull }); field(encode, Reg, high_side) when Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1' -> 16#00; field(encode, Reg, high_side_2x) when Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1' -> 16#01; field(encode, Reg, active_clamp_push_pull) when Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1' -> 16#10; field(encode, Reg, simple_push_pull) when Reg == 'DoMode4'; Reg == 'DoMode3'; Reg == 'DoMode2'; Reg == 'DoMode1' -> 16#11; field(decode, Reg, Value) when Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1' -> pick( Value, { #{current_limit => {milliampere, 600}, inrush => {millisecond, 20}}, #{current_limit => {milliampere, 130}, inrush => {millisecond, 50}}, #{current_limit => {milliampere, 300}, inrush => {millisecond, 40}}, #{current_limit => {milliampere, 1200}, inrush => {millisecond, 10}} } ); field(encode, Reg, #{ current_limit := {milliampere, 600}, inrush := {millisecond, 20} }) when Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1' -> 16#00; field(encode, Reg, #{ current_limit := {milliampere, 130}, inrush := {millisecond, 50} }) when Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1' -> 16#01; field(encode, Reg, #{ current_limit := {milliampere, 300}, inrush := {millisecond, 40} }) when Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1' -> 16#10; field(encode, Reg, #{ current_limit := {milliampere, 1200}, inrush := {millisecond, 10} }) when Reg == 'CL4'; Reg == 'CL3'; Reg == 'CL2'; Reg == 'CL1' -> 16#11; field(decode, _Name, 0) -> false; field(decode, _Name, 1) -> true; field(encode, _Name, false) -> 0; field(encode, _Name, true) -> 1; field(encode, Name, Value) -> error({invalid_field_value, Name, Value}). boolean(0) -> false; boolean(1) -> true. pick(Index, Values) -> element(Index + 1, Values).