I think it's better to create a separate function to setup the MCP230XX ISRs and associate with a GPIO pin.
No need to configure the ISR for every separate zyncoder, as it's a "per-chip" configuration. It should be initialized in the "init_zyncoder" function.
As you can see, currently this function initialize the mcp23008 like that:
mcp23008Setup (100, 0x20);
Currently there is no ISR catching for mcp23008 and polling is used for switches. Probably this is too slow to manage "rotaries", but i have not tried. Anyway, it would have poor performance and this is the reason we want to use interrupts
I would add extra parameters for telling the function if we use the MCP23008 or MCP23017, and the ISR GPIO pins.
If the ISR GPIO pins are "-1", then polling is used for switches (init_poll_zynswitches). If the ISR GPIO pins are >=0, then ISR functions will be associated (to implement!). This ISR function should read the corresponding ports, detect what port has changed its value and call the corresponding "update" function. Perhaps a "custom" update function must be implemented for optimal performance. In such a case, i woull try to abstract some code from the normal "update" function to avoid code redundance and improve maintainance.
Of course, i would help you as much as i can. Don't doubt to ask!