GPIO is uncontrolled when setting device-tree node without pinctrl-names - linux-device-driver

I'm working on imx8mm and testing GPIO with Linux kernel v4.14.98.
Device tree node is:
&iomuxc {
pinctrl-names = "default";
...
imx8mm-evk {
pinctrl_gpio_plural: gpiopluralgrp {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO11_GPIO1_IO11 0x41
>;
};
};
};
...
plural {
compatible = "gpio-plural";
/* pinctrl-names = "default"; */
pinctrl-0 = <&pinctrl_gpio_plural>;
reset-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
};
and I wrote a driver to testing this
static int gpio_plural_probe(struct platform_device *pdev)
{
struct gpio_plural_data *drvdata;
drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL);
if (drvdata == NULL)
return -ENOMEM;
drvdata->reset = devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(drvdata->reset)) {
printk("Error: reset not found\n");
return -EINVAL;
}
gpiod_set_value(drvdata->reset, 0);
mdelay(100);
gpiod_set_value(drvdata->reset, 1);
mdelay(100);
gpiod_set_value(drvdata->reset, 0);
mdelay(100);
gpiod_set_value(drvdata->reset, 1);
return 0;
}
However, I can't control GPIO pin when I comment pinctrl-names as device tree shown above. The GPIO pin always remains high.
In devicetree.c, the statename would be replaced to propname suffix, which in here is "0". But it just a constant name which could be any string. So my question is why I can't control GPIO pin without setting pinctrl-names?

It can't be any name, most of the node will have pinctrl-names = "default"; because this make pinctrl-0 the default state for the pins of the device.
This is actually quite important because the device core will use that to retrieve and set the proper state before probing the device, see pinctrl_bind_pins. It does:
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
PINCTRL_STATE_DEFAULT);
where PINCTRL_STATE_DEFAULT is "default".
Then, it selects the state with:
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
If you don't want to use the default name, then, you'll have to select the proper state in your driver.
Other generic state names are:
#define PINCTRL_STATE_DEFAULT "default"
#define PINCTRL_STATE_INIT "init"
#define PINCTRL_STATE_IDLE "idle"
#define PINCTRL_STATE_SLEEP "sleep"

Related

STM32WL55JC1 - HAL ADC wont change channels

What I want to accomplish
So I want to accomplish the following:
I have 3 FreeRTOS-Threads which all shall read one of 3 (5) channels of my ADC. I want to poll the ADC. The Threads then enter the read value into a FreeRTOS-queue.
My code so far
I have the following functions:
ADC initialisation
void MX_ADC_Init(void)
{
hadc.Instance = ADC;
hadc.Init.Resolution = ADC_RESOLUTION_12B;
hadc.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV4;
hadc.Init.ScanConvMode = DISABLE;
hadc.Init.ContinuousConvMode = DISABLE;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.NbrOfConversion = 1;
hadc.Init.DMAContinuousRequests = DISABLE;
hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc.Init.LowPowerAutoPowerOff = DISABLE;
hadc.Init.LowPowerAutoWait = DISABLE;
if (HAL_ADC_Init(&hadc) != HAL_OK)
{
Error_Handler();
}
for(int ch = 0; ch < GPIO_AI_COUNT; ch++)
{
ADC_Select_Ch(ch);
}
}
GPIO initialisation
GPIO_InitTypeDef GpioInitStruct = {0};
GpioInitStruct.Pin = GPIO_AI1_PIN | GPIO_AI2_PIN | GPIO_AI3_PIN | GPIO_AI4_PIN | GPIO_AI5_PIN;
GpioInitStruct.Pull = GPIO_NOPULL;
GpioInitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GpioInitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOB, &GpioInitStruct);
Where the GPIO_AI2_PIN definition is defined as:
/* Analog Inputs ----------------------------------------------------------- */
#define GPIO_AI_COUNT 5
#define GPIO_AI1_PIN GPIO_PIN_3
#define GPIO_AI1_PORT GPIOB
#define GPIO_AI1_CH ADC_CHANNEL_2 /* ADC_IN2, Datasheet P. 51 */
#define GPIO_AI2_PIN GPIO_PIN_4
#define GPIO_AI2_PORT GPIOB
#define GPIO_AI2_CH ADC_CHANNEL_3 /* ADC_IN3, Datasheet P. 51 */
#define GPIO_AI3_PIN GPIO_PIN_14
#define GPIO_AI3_PORT GPIOB
#define GPIO_AI3_CH ADC_CHANNEL_1 /* ADC_IN1, Datasheet P. 55 */
#define GPIO_AI4_PIN GPIO_PIN_13
#define GPIO_AI4_PORT GPIOB
#define GPIO_AI4_CH ADC_CHANNEL_0 /* ADC_IN0, Datasheet P. 55 */
#define GPIO_AI5_PIN GPIO_PIN_2
#define GPIO_AI5_PORT GPIOB
#define GPIO_AI5_CH ADC_CHANNEL_4 /* ADC_IN4, Datasheet P. 54 */
Changing channel
void ADC_Select_Ch(uint8_t channelNb)
{
adcConf.Rank = ADC_RANKS[channelNb];
adcConf.Channel = GPIO_AI_CH[channelNb];
adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
{
Error_Handler();
}
}
Where ADC_RANKS and GPIO_AI_CH are static arrays of the channels and ranks I want to use. The ranks increase with every channel.
Reading a channel
uint32_t ADC_Read_Ch(uint8_t channelNb)
{
uint32_t adc_value = 0;
ADC_Select_Ch(channelNb);
HAL_ADC_Start(&hadc);
if(HAL_OK == HAL_ADC_PollForConversion(&hadc, ADC_CONVERSION_TIMEOUT))
{
adc_value = HAL_ADC_GetValue(&hadc);
}
HAL_ADC_Stop(&hadc);
printf("Ch%d / %x) %d\r\n", channelNb, adcConf.Channel, adc_value);
return adc_value;
}
The problem
No matter what I try, the ADC only ever reads in the channel before the last channel I defined. Every time a conversion happens, the method HAL_ADC_GetValue(...) returns only the value of one channel, one, which I haven't even "selected" with my method.
What I've tried so far
I tried several different things:
Change NumberOfConversions
Change ScanMode, ContinuousConvMode, Overrun, EOCSelection, etc.
Use only Rank "1" when choosing a channel
Not use HAL_ADC_Stop(...), that however resulted in a failure (error handler was called)
Using the read functions etc. in the main(), not in a FreeRTOS thread - this also resulted in only one channel being read.
Change GPIO setup
Make the adcConfig global and public, so that maybe the config is shared among the channel selections.
Different clock settings
"Disabling" all other channels but the one I want to use (*)
Several other things which I've already forgotten
There seems to be one big thing I completely miss. Most of the examples are with one of the STM32Fxx microcontrollers, so maybe the ADC hardware is not the same and I can't do it this way. However, since I am using HAL, I should be able to do it this way. It would be weird, if it wouldn't be somehow the same across different uC families.
I really want to use polling, and ask one channel of the ADC by using some kind of channel selection, so that I can read them in different FreeRTOS tasks.
Disabling channels
I tried "disabling" channels but the one I've used with this function:
void ADC_Select_Ch(uint8_t channelNb)
{
for(int ch = 0; ch < GPIO_AI_COUNT; ch++)
{
adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
adcConf.Channel = GPIO_AI_CH[ch];
adcConf.Rank = ADC_RANK_NONE;
if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
{
Error_Handler();
}
}
adcConf.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
adcConf.Channel = GPIO_AI_CH[channelNb];
if (HAL_ADC_ConfigChannel(&hadc, &adcConf) != HAL_OK)
{
Error_Handler();
}
}
Can anyone help me? I'm really stuck, and the Reference Manual does not provide a good "guide" on how to use it. Only technical information, lol.
Thank you!
I think your general approach seems reasonable. I've done something similar on a project (for an STM32F0), where I had to switch the ADC between two channels. I think you do need to disable the unused channels. Here is some code verbatim from my project:
static void configure_channel_as( uint32_t channel, uint32_t rank )
{
ADC_ChannelConfTypeDef sConfig = { 0 };
sConfig.Channel = channel;
sConfig.Rank = rank;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if ( HAL_ADC_ConfigChannel ( &hadc, &sConfig ) != HAL_OK )
{
dprintf ( "Failed to configure channel\r\n" );
}
}
void adc_configure_for_head( void )
{
configure_channel_as ( ADC_CHANNEL_0, ADC_RANK_CHANNEL_NUMBER );
configure_channel_as ( ADC_CHANNEL_6, ADC_RANK_NONE );
}
void adc_configure_for_voltage( void )
{
configure_channel_as ( ADC_CHANNEL_6, ADC_RANK_CHANNEL_NUMBER );
configure_channel_as ( ADC_CHANNEL_0, ADC_RANK_NONE );
}
uint16_t adc_read_single_sample( void )
{
uint16_t result;
if ( HAL_ADC_Start ( &hadc ) != HAL_OK )
dprintf ( "Failed to start ADC for single sample\r\n" );
if ( HAL_ADC_PollForConversion ( &hadc, 100u ) != HAL_OK )
dprintf ( "ADC conversion didn't complete\r\n" );
result = HAL_ADC_GetValue ( &hadc );
if ( HAL_ADC_Stop ( &hadc ) != HAL_OK )
dprintf ( "Failed to stop DMA\r\n" );
return result;
}
My project was bare-metal (no-OS) and had a single thread. I don't know enough about how your tasks are scheduled, but I'd be concerned that they might be "fighting over" the ADC if there is a chance they could be run concurrently (or pre-empt each other). Make sure the entire configure / read sequence is protected by some kind of mutex or semaphore.
EDIT: I notice a bug in your "Disabling channels" code, where you don't seem to set the rank of your enabled channel. I don't know if that is a transcription error, or an actual bug.
Ranks are used to sort the ADC channels for cases of continuous measurrements or channel scans. HAL_ADC_PollForConversion only works on a single channel and somehow needs to now which channel to pick, therefore it will use the one with the lowest rank. To configure a specific channel to be measured once, set its rank to ADC_REGULAR_RANK_1.
No need to disable any other channels, but remember to properly configure the ranks of all channels if you want to switch to channel scanning or continuous measurements.
HAL_ADC_ConfigChannel(&hadc1, &channel_config) only updates the configuration of the channel itself but does not update the configuration of the ADC peripheral itself. So it has to be understood as "configure this channel" and not as "configure ADC to use this channel"

What does the variable and value of DP83867_RGMIIDCTL_2_25_NS in device tree correspond to?

I am a newbie to the embedded linux and device-tree world. I am trying to modify the device tree of ethernet phy from TI. I am interested to know what values the following variables <DP83867_RGMIIDCTL_2_25_NS>, <DP83867_RGMIIDCTL_2_75_NS> and <DP83867_PHYCR_FIFO_DEPTH_4_B_NIB> correspond to. It appears to be delay values, but I searched through the datasheet (https://www.ti.com/lit/ds/symlink/dp83867ir.pdf?HQS=dis-mous-null-mousermode-dsf-pf-null-wwe&DCM=yes&ref_url=https%3A%2F%2Fwww.mouser.co.uk%2F&distId=26) and couldn't find any reference to such variable.
Can someone with some experience in device-tree explain where these variable are coming from and how to change/configure such register values.
&gem3 { /* required by spec */
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gem3_default>;
phy-handle = <&phy0>;
phy-mode = "rgmii-id";
mdio: mdio {
#address-cells = <1>;
#size-cells = <0>;
reset-gpios = <&gpio 38 GPIO_ACTIVE_LOW>;
reset-delay-us = <2>;
phy0: ethernet-phy#1 {
#phy-cells = <1>;
reg = <1>;
ti,rx-internal-delay = <DP83867_RGMIIDCTL_2_25_NS>;
ti,tx-internal-delay = <DP83867_RGMIIDCTL_2_75_NS>;
ti,fifo-depth = <DP83867_PHYCR_FIFO_DEPTH_4_B_NIB>;
ti,dp83867-rxctrl-strap-quirk;
};
};
};
What does the variable and value of DP83867_RGMIIDCTL_2_25_NS in device tree correspond to?
From https://elixir.bootlin.com/linux/latest/A/ident/DP83867_RGMIIDCTL_2_25_NS it corresponds to a macro with the value 0x8.
#define DP83867_RGMIIDCTL_2_25_NS 0x8

STM32H743ZI NUCLEO 144 & LWIP - Can't Ping The Board

Hope all is going well.
I'm trying to ping STM32H743ZI NUCLEO 144 using LWIP middle-ware. Code generated by CubeMX.
Configurations:
Set the HCLK to 400 MHz
Enabled the CPU ICache and DCache (under Cortex_M7 Configuration)
Enabled MPU (Region0, Region1 & Region2)
Enabled LWIP
Selected LAN8742 as the Driver_PHY (under LwIP>Platform Settings)
DHCP disabled (IP, MASK: 255,255,255,000 , Gateway: Modem IP)
RTOS disabled
LWIP_HTTPD, LWIP_HTTPD_CGI enabled
LWIP_HTTPD_SSI enabled
LWIP_HTTPD_MAX_TAG_NAME_LEN set to 16
ICMP enabled (LWIP_BROADCAST_PING and LWIP_MULTICAST_PING in LwIP Key Options>IPMP Options).
Code Generated for Keil V5
MX_LWIP_Process added to the main function in While loop.
while(1)
{
MX_LWIP_Process();
}
I don't know how should I configure the CubeMX or change the generated code to be able to ping my board.
My_File
this will probably help you (it did for me):
Information about this issue can be found here.
https://community.st.com/s/article/FAQ-Ethernet-not-working-on-STM32H7x3
Memory buffers need to be assigned to RAM that can be accessed by the Ethernet
peripheral.
You may need to adjust the tour stack/heap size.
The default Ethernet GPIOs speed may be too low.
You may need to configure the MPU.
You may likely need to change your linker script.
On this page you will find good information:
https://github.com/MX-Master/STM32H7_Nucleo-H743ZI_Ethernet_LwIP
The HAL_Delay mentioned may not be required, though.
In the file lan8742.c (driver), I added an extra line for the LAN8742_Init function, around line 190, to set auto-negotiation:
// Link did not come up after HW reset.
pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, LAN8742_BCR_AUTONEGO_EN);
So that function looks like:
// Used in ethernetif.c, 363, static void low_level_init(struct netif *netif)
int32_t LAN8742_Init(lan8742_Object_t *pObj)
{
uint32_t tickstart = 0, regvalue = 0, addr = 0;
int32_t status = LAN8742_STATUS_OK;
if(pObj->Is_Initialized == 0)
{
if(pObj->IO.Init != 0)
{
/* GPIO and Clocks initialization */
pObj->IO.Init();
}
/* for later check */
pObj->DevAddr = LAN8742_MAX_DEV_ADDR + 1;
/* Get the device address from special mode register */
for(addr = 0; addr <= LAN8742_MAX_DEV_ADDR; addr ++)
{
if(pObj->IO.ReadReg(addr, LAN8742_SMR, &regvalue) < 0)
{
status = LAN8742_STATUS_READ_ERROR;
/* Can't read from this device address
continue with next address */
continue;
}
if((regvalue & LAN8742_SMR_PHY_ADDR) == addr)
{
pObj->DevAddr = addr;
status = LAN8742_STATUS_OK;
break;
}
}
if(pObj->DevAddr > LAN8742_MAX_DEV_ADDR)
{
status = LAN8742_STATUS_ADDRESS_ERROR;
}
/* if device address is matched */
if(status == LAN8742_STATUS_OK)
{
/* set a software reset */
if(pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, LAN8742_BCR_SOFT_RESET) >= 0)
{
/* get software reset status */
if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &regvalue) >= 0)
{
tickstart = pObj->IO.GetTick();
/* wait until software reset is done or timeout occurred */
while(regvalue & LAN8742_BCR_SOFT_RESET)
{
if((pObj->IO.GetTick() - tickstart) <= LAN8742_SW_RESET_TO)
{
if(pObj->IO.ReadReg(pObj->DevAddr, LAN8742_BCR, &regvalue) < 0)
{
status = LAN8742_STATUS_READ_ERROR;
break;
}
}
else
{
status = LAN8742_STATUS_RESET_TIMEOUT;
}
}
}
else
{
status = LAN8742_STATUS_READ_ERROR;
}
}
else
{
status = LAN8742_STATUS_WRITE_ERROR;
}
}
}
// Jack 2019-03-25, Link did not come up after HW reset.
pObj->IO.WriteReg(pObj->DevAddr, LAN8742_BCR, LAN8742_BCR_AUTONEGO_EN);
if(status == LAN8742_STATUS_OK)
{
tickstart = pObj->IO.GetTick();
/* Wait for 2s to perform initialization */
while((pObj->IO.GetTick() - tickstart) <= LAN8742_INIT_TO)
{
}
pObj->Is_Initialized = 1;
}
return status;
}

Not able to read from an external EEPROM using the STM32F103C8

I'm trying to write and read from an external EEPROM. There is a start bit (SB) followed by an opcode, then a 6-bit address and then the actual data. I've combined the SB and opcode into one byte that I can send as a start condition. I'm able to enable, erase and then write to the EEPROM. I'm assuming this is working since the HAL functions return HAL_OK and I can see the valid waveforms on the scope.
What I can't seem to do is read the data back. For the READ operation I don't see any waveforms on the scope. The number of clock cycles required is odd-numbered and not in multiples of 8. I don't know how I can send odd number of clock cycles since all the data is either 8, 16 or 32-bit. Wherever there are 25 or 29 clock cycles need, I seem to be sending 32 and where the required cycles are 9, I seem to be sending 16. I'm really hoping to avoid bit-banging as suggested in this thread.
Here is the main code:
int main(void)
{
HAL_Init();
MX_GPIO_Init();
MX_SPI1_Init();
__HAL_SPI_ENABLE(&hspi1);
// pull the CS pin high to select the EEPROM (active HIGH)
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
HAL_Delay(10);
// Enable the EEPROM
enable_status = Enable_EEPROM(&EEPROM_SPI_PORT);
HAL_Delay(10);
// Erase the value at address 0x00
erase_status = Erase_EEPROM(&EEPROM_SPI_PORT, addr);
HAL_Delay(10);
// Write data 0xABCD at addr 0x00
write_status = Write_EEPROM(&EEPROM_SPI_PORT, addr, tx_data);
HAL_Delay(10);
// Disabling the EEPROM (with an EWDS) after a WRITE as described in the datasheet
disable_status = Disable_EEPROM(&EEPROM_SPI_PORT);
HAL_Delay(10);
// Re-enabling it
enable_status = Enable_EEPROM(&EEPROM_SPI_PORT);
HAL_Delay(10);
// Read from the EEPROM. This part isn't working.
read_status = Read_EEPROM(&EEPROM_SPI_PORT, addr, rx_data);
HAL_Delay(10);
// Pull the CS pin low to deselect the chip again.
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
while (1)
{
}
}
The SPI is initialized to handle 16-bit data values
SPI_HandleTypeDef hspi1;
/* SPI1 init function */
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
These are the EEPROM functions
#define ERASE 0x07 // erase specific memory location. This is followed by the 8-bit address and then by the 16-bit data.
#define READ 0x06 // read the memory location.
#define WRITE 0x05 // write to the memory location
#define EEPROM_SPI_PORT hspi1
extern SPI_HandleTypeDef EEPROM_SPI_PORT;
//Enable the EEPROM
//Accepts: SPI handle
//Returns: Success or failure of the enable operation
uint8_t Enable_EEPROM (SPI_TypeDef *spi_handle) {
uint16_t ewen = (0x04 << 8) | 0b00110000;
if (HAL_SPI_Transmit(spi_handle, &ewen, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Disable the EEPROM
//Accepts: SPI handle
//Returns: Success or failure of the disable operation
uint8_t Disable_EEPROM (SPI_TypeDef *spi_handle) {
uint16_t ewds = (0x04 << 8) | 0b00000000;
if (HAL_SPI_Transmit(spi_handle, &ewds, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Read from the EEPROM
//Accepts: SPI handle, memory address and data buffer where the read value will be stored
//Returns: Success or failure of read operation
uint8_t Read_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint16_t data) {
uint16_t write_package;
write_package = (READ << 8 | addr);
// if (HAL_SPI_Transmit(spi_handle, &write_package, 1, HAL_MAX_DELAY) == HAL_OK) {
// HAL_Delay(10);
// if (HAL_SPI_Receive(spi_handle, &data, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
// else return FALSE;
// }
if (HAL_SPI_TransmitReceive(spi_handle, &write_package, &data, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Write to the EEPROM
//Accepts: SPI handle, memory address and data to be written
//Returns: Success or failure of write operation
uint8_t Write_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint16_t data) {
uint16_t write_package[2];
write_package[0] = (WRITE << 8 | addr);
write_package[1] = data;
if (HAL_SPI_Transmit(spi_handle, write_package, 2, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
//Erase a specific memory address from the EEPROM
//Accepts: SPI handle and the memory address to be erased
//Returns: Success or failure of erase operation
uint8_t Erase_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr) {
uint16_t write_package;
write_package = (ERASE << 8 | addr);
if (HAL_SPI_Transmit(spi_handle, &write_package, 1, HAL_MAX_DELAY) == HAL_OK) return TRUE;
else return FALSE;
}
EDIT: I’ve attached waveforms here as well.
Enable
Erase
Write
Without looking through your code in detail, I've spotted a possible problem: In order to complete an SPI operation, the chip select (CS) line usually needs to be pulled low before and set high again after every operation.
So, the EEPROM functions in your driver code probably need to first set the CS pin low, do some SPI operation, and set it high again after that.
For convenience, I usually add some simple helper functions to the driver source file:
static GPIO_TypeDef *_cs_port;
static uint16_t _cs_pin;
static void _chip_select(void)
{
HAL_GPIO_WritePin(_cs_port, _cs_pin, GPIO_PIN_RESET);
}
static void _chip_deselect(void)
{
HAL_GPIO_WritePin(_cs_port, _cs_pin, GPIO_PIN_SET);
}
In that case, I usually intialize the driver and and keep track of the peripheral instance and chip select GPIO, similar to this:
static SPI_HandleTypeDef *_spi;
static uint8_t _init = 0;
int8_t eeprom_init(
SPI_HandleTypeDef *spi,
GPIO_TypeDef *gpio_cs_port,
uint16_t gpio_cs_pin)
{
if (_init)
return -1;
_spi = spi;
_cs_port = gpio_cs_port;
_cs_pin = gpio_cs_pin;
/* do initialization here */
_chip_deselect();
_init = 1;
return 0;
}
int8_t eeprom_clear(void)
{
if (!_init)
return -1;
/* do de-initialization here */
_spi = 0;
_cs_port = 0;
_cs_pin = 0;
_init = 0;
return 0;
}
int8_t eeprom_op_x(void)
{
if (!_init)
return -1;
_chip_select();
op_x(); /* todo */
_chip_deselect();
return 0;
}
I hope this helps :) ! There might be other issues in your hardware/software; this is probably not the full solution to your problem.
BTW: There are also ways to use hardware chip select (STM32 SPI peripheral), which I've never used (SPI / NSS in the reference manual). As far as I can tell, you also used SPI_NSS_SOFT in your SPI configuration, which requires you to manually set the chip select line.
BTW: Unrelated, but maybe of interest: ST provides simple HAL functions to access external I2C flash (HAL_I2C_Mem_*() functions).
edit 0 (more findings by skimming through code / datasheet):
Read_EEPROM() will not work like this, the data read from the bus isn't accessible outside the function's scope (C issue). Instead, a pointer to a read buffer could be passed to the function (or the read data could be returned as return value). For example like this: uint8_t Read_EEPROM (SPI_TypeDef *spi_handle, uint8_t addr, uint8_t *data, uint8_t byte_count)
In Read_EEPROM(): HAL_SPI_TransmitReceive() won't read the incoming bytes, when used like this. It receives and transmits at the same time. So it would make sense to first write the read / address command, and then start reading the incoming bytes (like in your code that has been commented out).
In Enable_/Disable_/Read_/Erase_EEPROM(): The number of bytes (size) seems to be wrong, it should be 2 instead of 1, in order to make HAL_SPI_Transmit() / HAL_SPI_TransmitReceive() transmit/receive the right number of bytes.
This IC does not seem to be well suited to be used with normal
SPI, since it requires a very specific bit sequence which is
not byte aligned (like you said). It might make sense to bit bang
the communication (like you've mentioned), and pay attention to every
little bit stated in the datasheet...
Since this seems to be an early test, I'd try to keep it as simple as possible, and get a first enable/write/read operation going, by bit-twiddling the same SPI pins by hand (reconfigured as normal GPIOs), so that the problems with the STM32's byte oriented SPI HAL functions won't get in your way. And then work towards a nice little driver... Maybe the STM32's SPI can still be used in some way, it's hard to tell for me right now...

spidev to read eeprom id

OS: Linux
I'm writing a spidev application in userspace to read EEPROM id. I have my device tree entry as following:
spi0: spi#ffda4000 {
compatible = "snps,dw-apb-ssi";
#address-cells = <1>;
#size-cells = <0>;
reg = <0xffda4000 0x100>;
interrupt-parent = <&intc>;
interrupts = <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>;
num-cs = <2>;
cs-gpios = <&porta 7 GPIO_ACTIVE_HIGH>, <&porta 0 GPIO_ACTIVE_HIGH>;
bus-num = <0>;
tx-dma-channel = <&pdma 16>;
rx-dma-channel = <&pdma 17>;
clocks = <&spi_m_clk>;
status = "disabled";
};
and then:
&spi0 {
status = "okay";
m25p10_spi#0 {
compatible = "m25p10";
reg = <0>; /* chip select */
spi-max-frequency = <20000000>;
/* m25p,fast-read; */
enable-dma = <0>;
};
spidev#0 {
compatible = "rohm,dh2228fv";
reg = <0>; /* chip select */
spi-max-frequency = <20000000>;
enable-dma = <0>;
};
};
Idea is to have spidev at same node as m25p10 so that when user space application open handle to "/dev/spidev0.0", it is actually talking to m25p10. But I can't get linux boot up. Is there anything wrong with this approach?
This will not work. One device - one definition in DT.
Also, why do you need to have spidev device? You already have m25p10_spi, which should show up as MTD device (something like /dev/mtd0), and there should be no problems accessing it from user space.
UPDATE:
It looks like OP wants to keep MTD and read device unique ID via RDID command, which is not supported by current m25p10 driver.
I might be wrong, but for me the easiest solution would be to extend the driver to create sysfs entry with RDID data, that is read during probing.
Some valuable resources:
LDD3
Linux kernel documentation